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获得更好解释的简单方法。由于我们都在strict
和warnings
祈使句下运行重要程序,因此很容易将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》模块非常实用。其中两个最常用的功能,isa
和 can
,在面向对象编程中几乎总是作为方法使用。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 上打开问题或拉取请求来帮助我们。