模块和包

在Perl中,模块(module)和包(package)是不同的东西。

模块

模块是你可以包含在另一个Perl文件(脚本或模块)中的一个.pm文件,是与.plPerl脚本语法完全相同的文本文件。一个示例模块文件可能位于C:\foo\bar\baz\Demo\StringUtils.pm或者/foo/bar/baz/Demo/StringUtils.pm,并且有如下内容:

  1. use strict;
  2. use warnings;
  3. sub zombify {
  4. my $word = shift @_;
  5. $word =~ s/[aeiou]/r/g;
  6. return $word;
  7. }
  8. return 1;

因为模块在被加载时会自顶向下执行,你需要在结尾处返回一个true表示加载成功。

为了让Perl解释器能够找到这些Perl模块文件,调用perl程序前,包含它们的目录名需要被添加到环境变量PERL5LIB中。列出包含这些模块的根目录,而不是其中的某些子目录或者模块本身:

  1. set PERL5LIB=C:\foo\bar\baz;%PERL5LIB%

或者

  1. 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,并且包含以下内容:

  1. use strict;
  2. use warnings;
  3. require Demo::StringUtils;
  4. print zombify("i want brains"); # "r wrnt brrrns"

注意,在这里我们用双冒号::作为目录的分隔符。

现在问题来了:如果main.pl包含很多require调用,而且每个被加载的模块又包含更多require调用,那我们要找到zombify()子程序最初的定义就太困难了。解决方案是使用包。

是用来声明子程序的命名空间。所有的子程序默认都被声明在当前包中,而程序开始执行的时候,你位于main包中,不过你可以用内置函数[package](http://perldoc.perl.org/functions/package.html)来切换包:

  1. use strict;
  2. use warnings;
  3. sub subroutine {
  4. print "universe";
  5. }
  6. package Food::Potatoes;
  7. # 没有冲突:
  8. sub subroutine {
  9. print "kingedward";
  10. }

注意,我们这里使用双冒号::作为命名空间的分隔符。

当你调用一个子程序的时候,你默认会调用当前包中的子程序。你也可以显示指定包的名字,我们继续上面的脚本,看看会发生什么:

  1. subroutine(); # "kingedward"
  2. main::subroutine(); # "universe"
  3. Food::Potatoes::subroutine(); # "kingedward"

所以对上面描述的问题的一个符合逻辑的解决方案就是把C:\foo\bar\baz\Demo\StringUtils.pm或者/foo/bar/baz/Demo/StringUtils.pm改为:

  1. use strict;
  2. use warnings;
  3. package Demo::StringUtils;
  4. sub zombify {
  5. my $word = shift @_;
  6. $word =~ s/[aeiou]/r/g;
  7. return $word;
  8. }
  9. return 1;

然后把main.pl改为:

  1. use strict;
  2. use warnings;
  3. require Demo::StringUtils;
  4. 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程序员总是遵循下面两个规则:

  1. Perl脚本(.pl文件)不应该包含package声明。
  2. Perl模块(.pm文件)必须包含且仅包含一个package声明,且包名与它的文件名、所在的位置一致。例如,模块Demo/StringUtils.pm必须由package Demo::StringUtils开头。

因此,你会发现实际工作中,绝大部分由可靠的第三方提供的“包”和“模块”的概念是可以交换混用的。然而,很重要的是,你千万不能把这个当做承诺,因为将来有一天你一定会碰上一个疯子写的代码。