模块和包
在Perl中,模块(module)和包(package)是不同的东西。
模块
模块是你可以包含在另一个Perl文件(脚本或模块)中的一个.pm
文件,是与.pl
Perl脚本语法完全相同的文本文件。一个示例模块文件可能位于C:\foo\bar\baz\Demo\StringUtils.pm
或者/foo/bar/baz/Demo/StringUtils.pm
,并且有如下内容:
use strict;
use warnings;
sub zombify {
my $word = shift @_;
$word =~ s/[aeiou]/r/g;
return $word;
}
return 1;
因为模块在被加载时会自顶向下执行,你需要在结尾处返回一个true表示加载成功。
为了让Perl解释器能够找到这些Perl模块文件,调用perl
程序前,包含它们的目录名需要被添加到环境变量PERL5LIB
中。列出包含这些模块的根目录,而不是其中的某些子目录或者模块本身:
set PERL5LIB=C:\foo\bar\baz;%PERL5LIB%
或者
export PERL5LIB=/foo/bar/baz:$PERL5LIB
一旦Perl模块被创建并且perl
知道如何找到它以后,你就可以使用内置函数[require](http://perldoc.perl.org/functions/require.html)
在Perl脚本中查找并执行它。比如,调用require Demo::StringUtils
使Perl解释器去逐个查找所有列在PERL5LIB
中的目录,看是否有叫做Demo/StringUtils.pm
的文件。我们的示例脚本可以叫做main.pl
,并且包含以下内容:
use strict;
use warnings;
require Demo::StringUtils;
print zombify("i want brains"); # "r wrnt brrrns"
注意,在这里我们用双冒号::
作为目录的分隔符。
现在问题来了:如果main.pl
包含很多require
调用,而且每个被加载的模块又包含更多require
调用,那我们要找到zombify()
子程序最初的定义就太困难了。解决方案是使用包。
包
包是用来声明子程序的命名空间。所有的子程序默认都被声明在当前包中,而程序开始执行的时候,你位于main
包中,不过你可以用内置函数[package](http://perldoc.perl.org/functions/package.html)
来切换包:
use strict;
use warnings;
sub subroutine {
print "universe";
}
package Food::Potatoes;
# 没有冲突:
sub subroutine {
print "kingedward";
}
注意,我们这里使用双冒号::
作为命名空间的分隔符。
当你调用一个子程序的时候,你默认会调用当前包中的子程序。你也可以显示指定包的名字,我们继续上面的脚本,看看会发生什么:
subroutine(); # "kingedward"
main::subroutine(); # "universe"
Food::Potatoes::subroutine(); # "kingedward"
所以对上面描述的问题的一个符合逻辑的解决方案就是把C:\foo\bar\baz\Demo\StringUtils.pm
或者/foo/bar/baz/Demo/StringUtils.pm
改为:
use strict;
use warnings;
package Demo::StringUtils;
sub zombify {
my $word = shift @_;
$word =~ s/[aeiou]/r/g;
return $word;
}
return 1;
然后把main.pl
改为:
use strict;
use warnings;
require Demo::StringUtils;
print Demo::StringUtils::zombify("i want brains"); # "r wrnt brrrns"
下面这些内容可要仔细阅读了。
在Perl语言中包和模块是彼此独立完全不同的两个功能,它们恰好都是用双冒号作为分隔符根本就是个掩人耳目的把戏。在一个脚本或者模块中多次切换包是可行的,在不用位置的多个文件中使用同一个包名也是可行的。调用require Foo::Bar
并不会去查找并且加载一个有package Foo::Bar
的文件,也不一定会加载定义在Foo::Bar
命名空间里的子程序。调用require Foo::Bar
仅仅表示加载一个名为Foo/Bar.pm
的问题,与其中有什么包的声明没有任何关系,也许那个文件中声明了package Baz::Qux
和其他乱七八糟的内容。
同样的,调用Baz::Qux::processThis()
子程序并不一定要声明在名叫Baz/Qux.pm
的文件里,它可能被定义在任何地方。
分离这两种功能可能是Perl中最糟糕的一个设计,而如果把它们视作分开的功能,将带来混乱,以及让人抓狂的代码。值得庆幸的是,主流的Perl程序员总是遵循下面两个规则:
- Perl脚本(
.pl
文件)不应该包含package
声明。 - Perl模块(
.pm
文件)必须包含且仅包含一个package
声明,且包名与它的文件名、所在的位置一致。例如,模块Demo/StringUtils.pm
必须由package Demo::StringUtils
开头。
因此,你会发现实际工作中,绝大部分由可靠的第三方提供的“包”和“模块”的概念是可以交换混用的。然而,很重要的是,你千万不能把这个当做承诺,因为将来有一天你一定会碰上一个疯子写的代码。