Perl核心的隐藏宝藏

Perl核心包含了许多小模块,可以帮助你完成工作。其中许多模块并不为人所知。甚至一些为人所知的模块也有一些常被忽视的不错功能。在本文中,我们将深入了解Perl核心中的许多隐藏宝藏。

blib

此模块允许您使用MakeMaker将要安装的版本。CPAN上的大多数发行版都遵循MakeMaker的构建技术。如果您正在编写具有构建系统的Perl模块,那么MakeMaker很可能与之有关。在命令行上进行测试很常见;我知道我经常这样做。这就是blib派上用场的地方之一。当我在命令行上运行测试套件(你们都有测试套件,对吧?)时,我可以轻松地执行单个测试。

  perl -Mblib t/deepmagic.t

如果您正在构建别人的模块并发现自己正在调试测试失败,那么可以使用blib以相同的方式。

diagnostics

PC Load Letter,这到底意味着什么?! – Micheal Bolton

当Perl解释器被强制得足够大时,它可以输出数百条错误消息。其中一些可能相当晦涩。在warnings祈使句下运行以下代码片段会产生警告:在程序.perl的第11行使用了未终止的<>>操作符

  $i <<< $j;

幸运的是,diagnostics是一种从Perl获得更好解释的简单方法。由于我们都在strictwarnings祈使句下运行重要程序,因此很容易将diagnostics添加到其中。

  use strict;
  use warnings;
  use diagnostics;

前面的代码片段现在产生了以下警告

  Unterminated <> operator at -e line 1 (#1)
    (F) The lexer saw a left-angle bracket in a place where it was expecting
    a term, so it's looking for the corresponding right-angle bracket, and
    not finding it.  Chances are you left some needed parentheses out
    earlier in the line, and you really meant a "less than".

  Uncaught exception from user code:
        Unterminated <> operator at program.perl line 11.

应仅将diagnostics祈使句用于开发(在那里它真正有用)。

Benchmark

对代码进行基准测试可能很困难。当尝试优化程序或例程时,您想要尝试几种方法并查看哪种方法更快。这正是Benchmark模块的用途。这样,您就不必自己计算开始和结束时间,通常您可以在短时间内进行高级性能分析。以下是一个尝试确定直接哈希切片或逐个检索哈希值哪个更快示例。

  use Benchmark;

  sub literal_slice {
    my %family = (
      Daughter => 'Evilina',
      Father => 'Casey',
      Mother => 'Chastity',
    );
    my ($mom, $dad) = @family{qw[Mother Father]};
  }

  sub one_at_a_time {
    my %family = (
      Daughter => 'Evelina',
      Father => 'Casey',
      Mother => 'Chastity',
    );
    my $mom = $family{Mother};
    my $dad = $family{Father};
  }

  timethese(
    5_000_000 => {
      slice       => \&literal_slice,
      one_at_time => \&one_at_a_time,
    },
  );

在我的工作硬件上,一台双核G4 PowerMac,答案似乎很明显。聪明和机智并不会对我们造成太大伤害。以下是输出结果。

  Benchmark: timing 5000000 iterations of one_at_time, slice...
  one_at_time: 53 wallclock secs (53.63 usr +  0.00 sys = 53.63 CPU) 
         @ 93231.40/s (n=5000000)
        slice: 56 wallclock secs (56.72 usr +  0.00 sys = 56.72 CPU) 
         @ 88152.33/s (n=5000000)

CGI::Pretty

你们中的许多人知道可以使用Perl编写HTML,实际上,这个技巧在CGI程序中经常使用。如果您使用过CGI模块创建HTML,那么很明显输出不是供人类解析的。输出“仅浏览器”的特性使得调试几乎不可能。

  use CGI qw[:standard];

  print header,
    start_html( 'HTML from Perl' ),
    h2('Writiing HTML using Perl' ),
    hr,
    p( 'Writing HTML with Perl is simple with the CGI module.' ),
    end_html;

前面的程序产生了以下难以理解的输出。

  Content-Type: text/html; charset=ISO-8859-1

  <?xml version="1.0" encoding="iso-8859-1"?>
  <!DOCTYPE html
          PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
           "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">;
  <html xmlns="http://www.w3.org/1999/xhtml"; lang="en-US">
  <head><title>HTML from Perl</title></head><body><h2>Writing 
  HTML using Perl</h2><hr /><p>Writing HTML with Perl is simple with the 
  CGI module.</p></body></html>

将第一行更改为use CGI::Pretty qw[:standard];,我们的输出现在可管理。

  Content-Type: text/html; charset=ISO-8859-1

  <?xml version="1.0" encoding="iso-8859-1"?>
  <!DOCTYPE html
          PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
           "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">;
  <html xmlns="http://www.w3.org/1999/xhtml"; lang="en-US">
  <head><title>HTML from Perl</title>
  </head><body>
  <h2>
          Writing HTML using Perl
  </h2>
  <hr><p>
          Writing HTML with Perl is simple with the CGI module.
  </p>
  </body></html>

虽然不像我希望的那样吸引人,但有很多自定义可以完成,所有这些都在CGI::Pretty文档中概述。

Class::ISA

类继承的世界是一个复杂而曲折的迷宫。此模块提供了一些函数来帮助我们导航迷宫。最常见的需求是函数super_path()。在处理复杂的OO层次结构时,super_path()可以帮助我们知道我们正在继承哪些类(这并不总是显而易见的),并找到方法声明。

我有一个需要Class::DBI的小项目,所以我在一个类上运行了super_path(),以确定Perl将如何搜索继承树以查找方法。

  perl -MJobSearch -MClass::ISA -le'print for 
      Class::ISA::super_path( "JobSearch::Job" )'

以下类列表的顺序是Perl搜索方法时的顺序。

  JobSearch::Object
  Class::DBI::mysql
  Class::DBI
  Class::DBI::__::Base
  Class::Data::Inheritable
  Class::Accessor
  Ima::DBI
  Class::WhiteHole
  DBI
  Exporter
  DynaLoader

现在,如果我对一个方法实现或者方法的来源有疑问,我有一个很好的列表可以查阅。Class::ISA故意省略了当前类(在这个例子中是JobSearch::Job),以及UNIVERSAL

这里有一个小技巧,可以帮助我找出哪些类可能实现了mk_accessors方法。

  perl -MJobSearch -MClass::ISA -le \
    'for (Class::ISA::super_path( "JobSearch::Job" )) { 
        print if $_->can("mk_accessors") }'

由于继承,列表中的所有类都可以调用mk_accessors,但并非所有类实际上都定义了mk_accessors。它仍然有助于缩小列表。

Class::ISA是在Perl Core的5.8.0版本中引入的。如果您使用的是较老的Perl,您可以从CPAN下载它。

Cwd

这个模块可以让您轻松地找到当前的工作目录。没有必要像我们很多人那样去壳,而是使用Cwd

  use Cwd;
  my $path = cwd;

Env

Perl通过全局的%ENV哈希提供了对环境变量的访问。对于许多应用程序来说,这已经足够好了。有时它可能会碍事。这就是Env模块的用武之地。默认情况下,这个模块将为您的环境中的所有变量创建全局标量。

  use Env;
  print "$USER uses $SHELL";

有些变量作为列表更有用。您可以通过指定导入列表来改变Env的行为。

  use Env qw[@PATH $USER];
  print "$USER's  path is @PATH";

另一个节省编写程序时间和精力的模块。

File::Path

这个模块有一个非常有用的函数叫做mkpath。使用mkpath,您可以在一次操作中创建多个目录层。在某些情况下,这可以将递归函数或循环结构简化为一个简单的函数调用。

  use File::Path;
  mkpath "/usr/local/apache/htdocs/articles/2003";

由于mkpath将创建任何需要的目录以便最终创建2003目录,因此不再需要大量的代码。

File::Spec::Functions

这个模块通过File::Spec模块实现了一个合理且有用的接口。File::Spec必须通过调用类方法使用,而File::Spec::Functions将这些方法转换为函数。有许多函数都是有用的(并且在File::Spec::Unix中有完整的文档)。这里有一些例子。

  use File::Spec::Functions qw[splitpath canonpath splitdir abs2rel];

  # split a path into logical pieces
  my ($volume, $dir_path, $file) = splitpath( $path );

  # clean up directory path
  $dir_path = canonpath $dir_path;

  # split the directories into a list
  my @dirs = splitdir $dir_path;

  # turn the full path into a relative path
  my $rel_path = abs2rel $path;

如您所见,使用File::Spec::Functions可以节省大量的编码时间。不要忘记,这些函数是可移植的,因为它们使用不同的符号为Perl运行的操作系统。

File::Temp

如果您需要一个临时文件,那么请使用File::Temp。这个模块将为Perl运行的操作系统找到一个合适的临时目录,并在该位置打开一个临时文件。这是Perl Core节省您时间的另一个例子。

  use File::Temp;
  my $fh = tempfile;

  print $fh "temp data";

这将为您打开一个临时文件并返回文件句柄,以便您写入。当您的程序退出时,临时文件将被删除。

FindBin

FindBin有一个小但有用的目的:找到正在运行的Perl脚本的原始目录。当程序被调用时,很难确定这个目录。如果一个程序调用了chdir,那么这可能更加困难。FindBin让这变得容易。

  use FindBin;
  my $program_dir = $FindBin::Bin;

Shell

Shell通过漂亮的功能包装了处理命令行时的丑陋之处。这里的效果是更漂亮的程序。以下是一个简单的示例。

  use Shell qw[ls du];
  use File::Spec::Functions qw[rel2abs];

  chomp( my @files = ls );
  foreach ( @files ) {
        print du "-sk", rel2abs $_;
  }

Time::localtime

这个模块允许localtime返回一个对象。该对象通过名称访问在列表上下文中由localtime返回的各个元素。这虽然节省不了我们多少编码时间,但可以节省我们去查阅文档的时间。

  use Time::localtime;
  my $time = localtime;
  print $time->year += 1900;

还有一个类似的模块叫做Time::gmtime,它为gmtime函数提供了相同的功能。

UNIVERSAL

《UNIVERSAL》模块非常实用。其中两个最常用的功能,isacan,在面向对象编程中几乎总是作为方法使用。isa 用于确定对象属于哪个类,而 can 则会告诉我们对象是否支持某种方法。这对于测试很有用。例如。

  use Time::localtime;
  my $time = localtime;

  if ( $time->isa( 'Time::localtime' ) ) {
    print "We have a Time::localtime object";
  }

  if ( $time->can( "year" ) ) {
    print "We can get the year from our object";
  }

UNIVERSAL 中还有一个不太为人所知的函数,即 VERSION。我经常需要知道已安装模块的版本,我发现自己经常编写一行代码:

  perl -MTest::More -le'print $Test::More::VERSION'

这并不像这样漂亮。

  perl -MTest::More -le'print Test::More->VERSION'

结论

Perl 内核有许多隐藏的奇迹,我刚刚在这里列出了一些。多年来,在内核中寻找有趣的函数和模块为我节省了很多工作。如果你想要进一步了解,请查阅 perlmodlib 手册页,以获取核心模块列表。无论你的兴趣是 CGI、I18N、Locale 还是 Math,你都可以在那里找到节省数小时工作的东西。

标签

反馈

这篇文章有什么问题吗?请通过在 GitHub 上打开问题或拉取请求来帮助我们。