单文件版的perl程序只能用于构建较小的脚本程序。当代码规模较大时,应该遵循下面两条规则来构建程序。这样能将程序的各个部分按功能一个一个地细化,便于维护,也便于后续开发。数组
能复用的代码放进函数 能复用的函数放进模块
名称空间用于组织逻辑逻辑代码和数据,一个名称空间由一个包名,包内的全部子程序名以及包变量构成,出了这个名称空间就没法访问该名称空间内的内容,除非将其导入。有了包和名称空间,就能够避免名称冲突问题。函数
包的名称由0个或多个双冒号分隔,如下都是有效的包名称:测试
File::Find::Rule
Module::Starter
DBIx::Class
Moose
aliased
File::Find
和File
模块没有关系,File::Find::Rule
和File::Find
模块也没有任何关系,最多可能的关系是一个做者开发的模块,方便区分,也可能不是同一个做者开发的,模块名都是彻底独立的。ui
模块名应尽可能避免使用小写字母命名(例如上面的aliased
模块),由于在使用use导入的时候,可能会被看成编译指示词。this
对于包名My::Number::Utilities
,通常来讲,它对应的文件是My/Number/Utilities.pm
,它一般位于lib目录下,即lib/My/Number/Utilities.pm
。其中pm后缀文件表示一个perl模块,一个模块中能够有多个包(实际上,一个包也能够跨多个模块文件)。尽管如此,但强烈建议一个模块文件提供一个包,另一个建议是模块文件名和包名称应该要保持一致,虽然这不是必须的。prototype
(区分:模块和包。模块是文件,包是模块内的程序(假设包在一个模块内))debug
建立一个lib/My/Number
目录,而后建立一个名为Utilities.pm
的空文件。调试
$ mkdir -p lib/My/Number $ touch lib/My/Number/Utilities.pm $ tree lib/ lib/ └── My └── Number └── Utilities.pm
将如下代码保存到Utilities.pm文件,其中is_prime子程序用于判断数字是否为质数(素数)。code
package My::Number::Utilities; use strict; use warnings; our $VERSION = 0.01; sub is_prime { my $number = $_[0]; return if $number < 2; return 1 if $number == 2; for ( 2 .. int sqrt($number) ) { return if !($number % $_); } return 1; } 1;
再在lib目录的父目录下建立一个perl程序文件listing_primes.pl,代码以下:对象
use strict; use warnings; use diagnostics; use lib 'lib'; # Perl we'll find modules in lib/ use My::Number::Utilities; my @numbers = qw( 3 2 39 7919 997 631 200 7919 459 7919 623 997 867 15 ); my @primes = grep { My::Number::Utilities::is_prime($_) } @numbers; print join ', ' => sort { $a <=> $b } @primes;
文件结构:
$ tree . ├── lib │ └── My │ └── Number │ └── Utilities.pm └── list_primes.pl
而后执行:
$ perl list_primes.pl 2, 3, 631, 997, 997, 7919, 7919, 7919
回到上面的模块文件Utilities.pm的代码部分,这里面包含了建立模块时的几个规范语句:
package My::Number::Utilities; use strict; use warnings; our $VERSION = 0.01; # 设置模块版本号 ...这里是模块主代码... 1;
第一行是包名My::Number::Utilities
。它定义了该语句后面的全部内容(除了少数内容,如use指定的编译指示strict、warnings)都属于这个包。在此模块文件中,整个文件直到文件尾部都属于这个包范围。若是这个包后面还发现了包定义语句,将进入新包的范围。例如:
package My::Math; use strict; use warnings; our $VERSION = 0.01; sub sum { my @numbers = @_; my $total = 0; $total += $_ foreach @numbers; return $total; } # same file, different package package My::Math::Strict; use Scalar::Util 'looks_like_number'; our $VERSION = 0.01; sub sum { my @numbers = @_; my $total = 0; $total += $_ foreach grep { looks_like_number($_) } @numbers; return $total; } 1;
上面的模块文件中定义了两个包,两个包中都定义了同名的sum()子程序,可是第一个sum子程序能够经过My::Number::sum(@numbers)
的方式调用,第二个sum子程序能够经过My::Math::Strict::sum()
的方式调用。但编译指示strict和warnings是属于整个文件的,也就是说两个包都会收到这两个指示的限定。
有时候想要限定包的做用域,只需将包放进一个代码块便可:
package My::Package; use strict; use warnings; our $VERSION = 0.01; { package My::Package::Debug; our $VERSION = 0.01; # this belongs to My::Package::Debug sub debug { # some debug routine } } # any code here belongs to My::Package; 1;
你可能已经注意到了,模块文件的尾部老是使用1;
结尾。当你定义一个模块的时候,这个模块文件必须返回一个真值,不然使用use导入模块的时候,将会出现编译错误(实际上require在运行时也会报错)。通常来讲,你们都喜欢在文件尾部使用一个1;
来表明真值,但若是你使用其它字符的话(如'one'),可能会给出warning。
通常来讲,当你须要导入一个模块时,你可能会使用use语句:
use My::Number::Utilities;
use语句的用途很广,对于模块方面的功能来讲,有如下几种相关操做:
use VERSION use Module VERSION LIST use Module VERSION use Module LIST use Module
其中use VERSION
告诉perl,运行最低多少版本的perl(也就是说能使用从哪一个版本以后的特性)。有几种描述版本号的形式,例如想要perl以version 5.8.1或更高版本运行:
use v5.8.1; use 5.8.1; use 5.008_001;
版本号前缀的"v"要求以3部分数值形式描述版本号(称为v-string),不建议使用这种描述形式,由于可能会出问题。
另外,当使用use 5.11.0;
或更高版本后,将直接隐含strict编译指示,也就是说无需再写use strict;
。
对于这几种形式的use语句:
use Module use Module LIST use Module VERSION use Module VERSION LIST
例如:
use Test::More;
Test::More
模块用于测试代码。假如想要使用这个模块中的某个功能subtest()
,但这个功能直到改模块的v0.96版才开始提供,所以你能够指定最低的模块版本号。
use Test::More 0.96; # 或 use Test::More v0.96.0;
当perl开始装载Test::More
的时候,会检查该模块的包变量$Test::More::VERSION
,若是发现版本低于0.96,将自动触发croak()。
强烈建议为每一个模块设置版本号our $VERSION=NUM;
,这样当发现某个版本(如0.01)的模块有bug后,使用该模块的程序能够经过最低版本号(如0.02)来避免这个bug。
our $VERSION = 0.01;
use Test::More
时,能够接受一个导入列表。当使用use装载一个模块的时候,perl会自动搜索一个名为import()的函数,而后将列表参数传递给import(),由import()实现导入的功能。所以,能够这样使用:
use Test::More tests => 13;
perl会将列表[tests,13]做为参数传递给Test::More::import()
。
若是只是装载模块,不想导入任何功能,能够传递一个空列表:
use Test::More ();
最后,能够结合版本号和导入的参数列表:
use Test::More 0.96 tests => 13;
除了使用use,还可使用require导入模块(此外,eval、do均可以导入)。use语句是在编译器进行模块装载的,而require是在运行时导入模块文件的。
require My::Number::Utilities;
通常来讲,除非必要,都只需使用use便可。但有时候为了延迟装载模块,可使用require。例如,使用Data::Dumper
模块调试数据,想要只在某处失败的时候装载该模块:
sub debug { my @args=@_; require Data::Dumper; Data::Dumper::Dumper(\@args); }
这样,只有在某处失败,开始调用debug()的时候,才会导入这个模块,其余时候都不会触发该模块,所以必须使用全名Data::Dumper::Dumper()
。
包变量有时候称为全局变量,虽然包自身是局部的,由于一个模块文件中能够定义多个包,在只有一个包的状况下,它们确实是等价的概念,但即便一个文件中多个包的状况下,包变量也是对全部外界可见的。
除了my修饰的对象,全部属性、代码都独属于各自所在的包(若是没有声明包,则是默认的main包),因此经过包名称能够找到包中的内容(my不属于包,因此不能访问)。
可使用彻底限定名称或our来声明属于本包的包变量,甚至不加任何修饰符,但不加修饰符会被use strict
阻止:
use strict; use warnings; use 5.010; package My::Number::Utilities; $My::Number::Utilities::PI=3.14; # 声明属于本包的包变量 # 或者 # our $PI=3.14 # 声明属于本包的包变量 # 或者 # $PI=3.14 # 也是声明包变量,但会被strict阻止而声明失败 say $My::Number::Utilities::PI;
可使用local和our两个修饰符修饰变量。如下是my、local、our的区别:
my和our的异同:
our和local的异同:
另外一种理解my/local/our的区别:
要访问某个包中的变量,可使用彻底限定名称$模块::变量
。但能够在包中使用our语句修饰一个变量,使得这个变量能够直接做为包变量覆盖词法变量,直到退出做用域为止。
例如,Data::Dumper
模块提供了控制模块行为的包变量:
use Data::Dumper; # sort hash keys alphabetically local $Data::Dumper::Sortkeys = 1; # tighten up indentation local $Data::Dumper::Indent = 1; print Dumper(\%hash);
若是想要将包变量被别的包访问,可让别的包经过彻底限定名称的形式。但这不是一个好主意,稍后会解释。不过如今,你能够访问这些包变量:
package My::Number::Utilities; use strict; use warnings; our $VERSION = 0.01; $My::Number::Utilities::PI = 3.14159265359; $My::Number::Utilities::E = 2.71828182846; $My::Number::Uitlities::PHI = 1.61803398874; # golden ratio @My::Number::Utilities::FIRST_PRIMES = qw( 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 ); sub is_prime { # } 1;
如你所见,定义了几个包变量,但这里隐藏了一个问题:$My::Number::Uitlities::PHI
这个包变量的包名称拼错了。为了不写全包名容易出错,因而使用our修饰词声明变量同时忽略包名:
our $PI = 3.14159265359; our $E = 2.71828182846; our $PHI = 1.61803398874; # golden ratio our @FIRST_PRIMES = qw( 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 );
这时在其它包中也能经过彻底限定名称访问该包中的变量。
但必须注意的是,直接定义包变量是能直接被其它可访问它的包修改的。例如,在list_primers.pl文件中从两个包访问My::Number::Utilities
包中的our $VERSION=0.01
:
#!/usr/bin/env perl use strict; use warnings; use diagnostics; use 5.010; use lib 'lib'; { use My::Number::Utilities; say "block1,1: ",$My::Number::Utilities::VERSION; # 输出:0.01 $My::Number::Utilities::VERSION =3; say "block1,2: ",$My::Number::Utilities::VERSION; # 输出:3 } say "line: ",$My::Number::Utilities::VERSION; # 输出:3 { use My::Number::Utilities; say "block2,1: ",$My::Number::Utilities::VERSION; # 输出:3 $My::Number::Utilities::VERSION=4; say "block2,2: ",$My::Number::Utilities::VERSION; # 输出:4 }
上面使用了两次use导入这个模块,但实际上只在编译期间导入了一次,因此每次访问和操做的对象都是同一个目标。
为了避免让其它包修改这种常量型的数值,能够经过子程序来定义它。例如:
sub pi {3.14};
而后这个值就成了只读的值了。在其它想要获取这个值的包中,只需执行这个函数便可:
package Universe::Roman; use My::Number::Utilities; my $PI = My::Number::Utilities::pi();
因此,除非必要,不要使用our定义包变量,以免被其它包修改。
定义好一个模块后,想要使用这个模块中的属性,可使用彻底限定名称的方式。但彻底限定名称毕竟比较长,写起来比较麻烦,也比较容易出错。
可使用Exporter模块来导出模块属性,而后在使用模块的其它文件中使用import()导入指定属性。
例如,My::Number::Utilities
模块的内容以下:
package My::Number::Utilities; use strict; use warnings; our $VERSION = 0.01; use base 'Exporter'; our @EXPORT_OK = qw(pi is_prime); # 导出属性 our %EXPORT_TAGS = ( all => \@EXPORT_OK ); # 按标签导出 sub pi() { 3.14166 } # 设置为null prototypes sub is_prime { my $number = $_[0]; return if $number < 2; return 1 if $number == 2; for ( 2 .. int sqrt($number) ) { return if !($number % $_); } return 1; } 1;
该模块将子程序pi()和is_prime()都进行了导出,此外还导出了一个名为all的标签,其中use base 'Exporter'
表示继承Exporter模块。对于非面向对象的模块来讲,能够不用使用继承的方式实现一样的效果use Exporter 'import';
。
而后其它程序就能够导入该模块已导出的子程序:
use My::Number::Utilities 'pi', 'is_prime'; use My::Number::Utilities 'is_prime'; use My::Number::Utilities qw(pi is_prime); # 建议该方法 use My::Number::Utilities (); # 什么都不导入
当其它程序导入模块的属性列表时,perl会调用Exporter::import()
方法,而后根据指定要导入的属性列表搜索模块My::Number::Utilities
模块的@EXPORT
、@EXPORT_OK
和%EXPORT_TAGS
变量,只有存在于@EXPORT_OK
和@EXPORT
中的属性才能被导出,但强烈建议不要使用@EXPORT
,由于它会导出全部函数给使用该模块的程序,使得程序没法控制、决定要导入哪些属性,这可能会无心中导入一个和当前程序中同名的函数并覆盖。
当导入的属性列表是一个空列表时(即上面代码的最后一行),表示不会调用import(),也就是什么都不会去导入,仅仅只是装载这个模块,这时若是想要引用该模块中的属性,必须写彻底限定名称。
前面使用%EXPORT_TAGS
定义了一个标签all:
our %EXPORT_TAGS = ( all => \@EXPORT_OK ); # 按标签导出
这是一个hash结构,hash的key是标签名,value是要导出的属性列表的引用。上面导出的是all标签,其值是\@EXPORT_OK
。当使用该模块的时候,就能够经过标签来导入:
use My::Number::Utilities ':all';
当模块中要导出的子程序较多的时候,使用标签对函数进行分类,这样在使用该模块导入属性时能够按照标签名导入而不用输入大量的函数名。例如,导出一大堆的标签:
our %EXPORT_TAGS = ( all => \@EXPORT_OK, constant => [qw(pi phi e)], # 导出常量 cgi => [qw(get_ip get_uri get_host)], # 导出CGI类的函数 );
而后导入的时候:
use My::Number::Utilities ':all'; use My::Number::Utilities qw(:constant :cgi);
空原型(null prototype):sub pi() { 3.14 }
当perl看到一个null prototype时,若是这个子程序的函数体很是简单,perl在编译时会尝试直接用这个函数体的返回值替换这个函数的调用。例如:
use My::Number::Utilities 'pi';
print pi; # pi在编译期间就会直接替换为3.14
对于常量的导出,你可能会常常看到这样的声明方式:
our @EXPORT_OK = qw(PI E PHI); use constant PI => 3.14159265359; use constant E => 2.71828182846; use constant PHI => 1.61803398874;
经过"constant"编译指示,常量会被建立为null prototype的子程序,也就是说,上面的代码和下面的代码是等价的:
our @EXPORT_OK = qw(pi e phi); sub pi() { 3.14159265359 } sub e() { 2.71828182846 } sub phi() { 1.61803398874 }