Perl5学习笔记-十四章-Perl5的包和模块
一、require函数
1、require函数和子程序库
2、用require指定Perl版本
二、包
1、包的定义
2、在包间切换
3、main包
4、包的引用
5、指定无当前包
6、包和子程序
7、用包定义私有数据
8、包和系统变量
9、访问符号表
三、模块
1、创建模块
2、导入模块
3、预定义模块
四、小结
一、require函数
用require函数可以把程序分割成多个文件并创建函数库。例如,在myfile.pl中有定义好的Perl函数,可用语句require ("myfile.pl"); 在程序中包含进来。当Perl解释器看到这一语句,就在内置数组变量@INC指定的目录中寻找文件myfile.pl。如果找到了,该文件中的语句就被执 行,否则程序终止并输出错误信息:
Can't find myfile.pl in @INC
作为子程序调用参数,文件中最后一个表达式的值成为返回值,require函数查看其是否为零,若为零则终止。例如myfile.pl最后的语句是:
print ("hello, world!\n");
$var = 0;
因为最后的语句值为零,Perl解释器输出下列错误信息并推出:
myfile.pl did not reture true value
可以用简单变量或数组元素等向require传递参数,如:
@reqlist = ("file1.pl", "file2.pl", "file3.pl");
require ($reqlist[$0]);
require ($reqlist[$1]);
require ($reqlist[$2]);
还可以不指定文件名,即:
require;
这时变量$_的值即作为文件名传递给require。
注:如果@INC中有多个目录中含有同一个文件,则只有第一个被包含。
1、require函数和子程序库
用require函数可以创建可用于所有Perl程序的子程序库,步骤如下:
a、确定存贮子程序库的目录;
b、将子程序抽取放到单独的文件中,将文件放到子程序库目录;
c、每个文件末尾加一句非零值的语句,最简单的办法是语句 1;
d、在主程序中用require包含一个或多个所需的文件。
e、运行主程序时,用 -I 选项指定子程序库目录,或者在调用require前将该目录添加到@INC数组中。
例如:假设目录/u/perldir中存有你的Perl子程序库,子程序mysub存贮在文件mysub.pl中。现在来包含上该文件:
unshift (@INC, "/u/perldir");
require ("mysub.pl");
对unshift的调用把目录/u/perldir添加到@INC数组,对require的调用将mysub.pl文件的内容包含进来作为程序的一部分。
注意:
1.应该使用unshift来向@INC中添加目录,而不是push。因为push增加到@INC的末尾,则该目录将被最后搜寻。
2.如果你的库文件名与/usr/local/lib/perl中的某文件同名,则不会被包含进来,因为require只包含同名文件中的第一个。
3.用require指定Perl版本。
Perl 5中,可以用require语句来指定程序运行所需的Perl版本。当Perl解释器看到require后跟着数字时,则只有其版本高于或等于该数字时才运行该程序。例如,下面语句表明只有Perl解释器为5.001版或更高时才运行该程序:
require 5.001;
二、包
Perl程序把变量和子程序的名称存贮到符号表中,perl的符号表中名字的集合就称为包(package)。
1、包的定义
在一个程序中可以定义多个包,每个包有一个单独的符号表,定义语法为:
package mypack;
此语句定义一个名为mypack的包,从此以后定义的所有变量和子程序的名字都存贮在该包关联的符号表中,直到遇到另一个package语句为止。
每个符号表有其自己的一组变量、子程序名,各组名字是不相关的,因此可以在不同的包中使用相同的变量名,而代表的是不同的变量。如:
$var = 14;
package mypack;
$var = 6;
第一个语句创建变量$var并存贮在main符号表中,第三个语句创建另一个同名变量$var并存贮在mypack包的符号表中。
2、在包间切换
在程序里可以随时在包间来回切换,如:
1: #!/usr/bin/perl
2:
3: package pack1;
4: $var = 26;
5: package pack2;
6: $var = 34;
7: package pack1;
8: print ("$var\n");
运行结果如下:
$ program
26
$
第三行定义了包pack1,第四行创建变量$var,存贮在包pack1的符号表中,第五行定义新包pack2,第六行创建另一个变量$var,存贮在包pack2的符号表中。这样就有两个独立的$var,分别存贮在不同的包中。第七行又指定pack1为当前包,因为包pack1已经定义,这样所有变量和子程序的定义和调用都为该包的符号表中存贮的名字。因此第八行对$var的调用为pack1包中的$var,其值为26。
3、main包
存贮变量和子程序的名字的缺省符号表是与名为main的包相关联的。如果在程序里定义了其它的包,当你想切换回去使用缺省的符号表,可以重新指定main包:
package main;
这样,接下来的程序就好象从没定义过包一样,变量和子程序的名字象通常那样存贮。
4、包的引用
在一个包中可以引用其它包中的变量或子程序,方法是在变量名前面加上包名和一个单引号,如:
package mypack;
$var = 26;
package main;
print ("$mypack'var\n");
这里,$mypack'var为mypack包中的变量$var。
注意:在Perl 5中,包名和变量名用双冒号隔开,即$mypack::var。单引号引用的方式仍然支持,但将来的版本中未必支持。
5、指定无当前包
在Perl 5中,可以用如下语句指定无当前包:
package;
这时所有的变量必须明确指出所属包名,否则就无效--错误。
$mypack::var = 21; #ok
$var = 21; #error - no current package
这种情况直到用package语句指定当前包为止。
6、包和子程序
包的定义影响到程序中的所有语句,包括子程序,如:
package mypack;
subroutine mysub {
local ($myvar);
# stuff goes here
}
这里,mysub和myvar都是包mypack的一部分。在包mypack外调用子程序mysub,则要指定包:$mypack'mysub。
可以在子程序中切换包:
package pack1;
subroutine mysub {
$var1 = 1;
package pack2;
$var1 = 2;
}
这段代码创建了两个变量$var1,一个在包pack1中,一个在包pack2中,包中的局域变量只能在其定义的子程序等语句块中使用,像普通的局域变量一样。
7、用包定义私有数据
包最通常的用途是用在含有子程序和子程序所使用的全局变量的文件中,为子程序定义这样的包,可以保证子程序使用的全局变量不可在其它地方使用,这样的数据即为私有数据。更进一步,可以保证包名不可在其它地方使用。私有数据例:
1 : package privpack;
2 : $valtoprint = 46;
3 :
4 : package main;
5 : # This function is the link to the outside world.
6 : sub printval {
7 : &privpack'printval();
8 : }
9 :
10: package privpack;
11: sub printval {
12: print ("$valtoprint\n");
13: }
14:
15: package main;
16: # return value for require
此子程序只有在调用printval后才能产生输出。该文件分为两个部分:与外界联系的部分和私有部分。前者为缺省的main包,后者为包privpack。第6~8行定义的子程序printval 可被其它程序或子程序调用。printval输出变量$valtoprint的值,此变量仅在包privpack中定义和使用。第15、16行确保其被其它程序用require语句包含后工作正常,15行将当前包设置回缺省包main,16行返回非零值使require不报错。
8、包和系统变量
下列变量即使从其它包中调用,也在main包中起作用:
•文件变量STDIN, STDOUT, STDERR 和 ARGV
•变量%ENV, %INC, @INC, $ARGV 和 @ARGV
•其它含有特殊字符的系统变量
9、访问符号表
在程序中查找符号表可用数组%_package,此处package为想访问的符号表所属的包名。例如%_main含有缺省的符号表。通常不需要亲自查找符号表。
三、模块
多数大型程序都分割成多个部件,每一部件通常含有一个或多个子程序及相关的变量,执行特定的一个或多个任务。集合了变量和子程序的部件称为程序模块。
1、创建模块
Perl 5中用包来创建模块,方法是创建包并将之存在同名的文件中。例如,名为Mymodult的包存贮在文件Mymodult.pm中(扩展名.pm表示 Perl Module)。下例的模块Mymodult含有子程序myfunc1和myfunc2及变量$myvar1和$myvar2。
1 : #!/usr/local/bin/perl
2 :
3 : package Mymodule;
4 : require Exporter;
5 : @ISA = qw(Exporter);
6 : @EXPORT = qw(myfunc1 myfunc2);
7 : @EXPORT_OK = qw($myvar1 $myvar2);
8 :
9 : sub myfunc1 {
10: $myvar1 += 1;
11: }
12:
13: sub myfunc2 {
14: $myvar2 += 2;
15: }
第3~7行是标准的Perl模块定义方式。第3行定义包,第4行包含内置Perl模块Exporter,6、7行进行子程序和变量的输出以与外界联系。第6行创建名为@EXPORT的特殊数组,该数组中的子程序可以被其它程序调用,这里,myfunc1和myfunc2可以被访问。其它任何在模块中定义但没有赋给数组@EXPORT的子程序都是私有的,只能在模块内部调用。第7行创建另一个名为@EXPORT_OK的特殊数组,其中含有可被外部程序访问的变量,这里含有$myvar1和$myvar2。
2、导入模块
将模块导入你的Perl程序中使用use语句,如下句导入了Mymodule模块:
use Mymodule;
这样,模块Mymodule中的子程序和变量就可以使用了。取消导入模块使用no语句,如下句取消了Mymodule模块的导入:
no Mymodule;
下面看一个导入模块和取消导入的例子,使用integer模块要求所有数字运算基于整数,浮点数在运算前均被转化为整数。
1: #!/usr/local/bin/perl
2:
3: use integer;
4: $result = 2.4 + 2.4;
5: print ("$result\n");
6:
7: no integer;
8: $result = 2.4 + 2.4;
9: print ("$result\n");
程序输出如下:
$ program
4
4.8
如果use或no语句出现在语句块中,则只在该块的有效范围内起作用,如:
use integer;
$result1 = 2.4 + 2.4;
if ($result1 == 4) {
no integer;
$result2 = 3.4 + 3.4;
}
$result3 = 4.4 + 4.4;
结果输出如下:
4
6.8
8
这里,no语句只在if语句中有效,出了if语句仍使用integer模块,因此4.4在做加法前被转化成了4。
3、预定义模块
Perl 5提供了许多有用的预定义模块,可以用use导入和no语句取消。下面是库中最有用的一些模块:
integer 使用整数运算
Diagnostics 输出较多的诊断信息(警告)
English 允许英文名用作系统变量的别名
Env 导入环境变量的Perl模块
POSIX POSIX标准(IEEE 1003.1)的Perl接口
Socket 装载C语言的套接字处理机制
Perl文档中有完整的预定义模块列表。
注:世界各地的Perl 5用户写了许多有用的模块,CPAN(Comprehensive Perl Archive Network)的Perl文档有其完整的列表。
通过 Perl 分发自带的工具 h2xs 可以很简单的创建一个 Perl 模块,可以在命令行模式键入 h2xs 来看看它的参数列表。
h2xs 语法格式:
$ h2xs -AX -n ModuleName
参数说明:
-A 忽略 autoload 机制
-X 忽略 XS 元素
-n 指定扩展模块的名字
如果模块在 Person.pm 文件中,使用以下命令:
$ h2xs -AX -n Person
执行以上程序将输出:
Writing Person/lib/Person.pm
Writing Person/Makefile.PL
Writing Person/README
Writing Person/t/Person.t
Writing Person/Changes
Writing Person/MANIFEST
Person 目录下可以看到新增加的目录及文件说明:
README :这个文件包含一些安装信息,模块依赖性,版权信息等。
Changes :这个文件作为你的项目的修改日志(changelog)文件。
Makefile.PL :这是标准的 Perl Makefile 构造器。用于创建 Makefile.PL 文件来编译该模块。
MANIFEST :本文件用于自动构建 tar.gz 类型的模块版本分发。这样你就可以把你的模块拿到 CPAN 发布或者分发给其他人。它包含了你在这个项目中所有文件的列表。
Person.pm :这是主模块文件,包含你的 mod_perl 句柄代码(handler code)。
Person.t :针对该模块的一些测试脚本。默认情况下它只是检查模块的载入,你可以添加一些新的测试单元。
t/ :测试文件
lib/ :实际源码存放的目录
可以使用 tar (Linux 上) 命令来将以上目录打包为 Person.tar.gz。
四、小结
本节转自骏马金龙的博客空间,感谢原作者。
名称空间用于组织逻辑逻辑代码和数据,一个名称空间由一个包名,包内的所有子程序名以及包变量构成,出了这个名称空间就无法访问该名称空间内的内容,除非将其导入。有了包和名称空间,就可以避免名称冲突问题。包的名称由0个或多个双冒号分隔,以下都是有效的包名称:
File::Find::Rule
Module::Starter
DBIx::Class
Moose
aliased
File::Find和File模块没有关系,File::Find::Rule和File::Find模块也没有任何关系,最多可能的关系是一个作者开发的模块,方便区分,也可能不是同一个作者开发的,模块名都是完全独立的。模块名应尽量避免使用小写字母命名(例如上面的aliased模块),因为在使用use导入的时候,可能会被当作编译指示词。
对于包名My::Number::Utilities,一般来说,它对应的文件是My/Number/Utilities.pm,它通常位于lib目录下,即lib/My/Number/Utilities.pm。其中pm后缀文件表示一个perl模块,一个模块中可以有多个包(实际上,一个包也可以跨多个模块文件)。尽管如此,但强烈建议一个模块文件提供一个包,另外一个建议是模块文件名和包名称应该要保持一致,虽然这不是必须的。
(区分:模块和包。模块是文件,包是模块内的程序(假设包在一个模块内))
创建一个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子程序用于判断数字是否为质数(素数)。
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 VS. require
一般来说,当需要导入一个模块时可能会使用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()。本站也有同类文章《理解use_require_do使用方法》。
包变量
包变量有时候称为全局变量,虽然包自身是局部的,因为一个模块文件中可以定义多个包,在只有一个包的情况下,它们确实是等价的概念,但即使一个文件中多个包的情况下,包变量也是对所有外界可见的。
除了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;
范围声明our-my-local
访问和修改包变量
要访问某个包中的变量,可以使用完全限定名称$模块::变量。但可以在包中使用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:
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导出模块属性
定义好一个模块后,想要使用这个模块中的属性,可以使用完全限定名称的方式。但完全限定名称毕竟比较长,写起来比较麻烦,也比较容易出错。可以使用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 }
1、require函数和子程序库
2、用require指定Perl版本
二、包
1、包的定义
2、在包间切换
3、main包
4、包的引用
5、指定无当前包
6、包和子程序
7、用包定义私有数据
8、包和系统变量
9、访问符号表
三、模块
1、创建模块
2、导入模块
3、预定义模块
四、小结
一、require函数
用require函数可以把程序分割成多个文件并创建函数库。例如,在myfile.pl中有定义好的Perl函数,可用语句require ("myfile.pl"); 在程序中包含进来。当Perl解释器看到这一语句,就在内置数组变量@INC指定的目录中寻找文件myfile.pl。如果找到了,该文件中的语句就被执 行,否则程序终止并输出错误信息:
Can't find myfile.pl in @INC
作为子程序调用参数,文件中最后一个表达式的值成为返回值,require函数查看其是否为零,若为零则终止。例如myfile.pl最后的语句是:
print ("hello, world!\n");
$var = 0;
因为最后的语句值为零,Perl解释器输出下列错误信息并推出:
myfile.pl did not reture true value
可以用简单变量或数组元素等向require传递参数,如:
@reqlist = ("file1.pl", "file2.pl", "file3.pl");
require ($reqlist[$0]);
require ($reqlist[$1]);
require ($reqlist[$2]);
还可以不指定文件名,即:
require;
这时变量$_的值即作为文件名传递给require。
注:如果@INC中有多个目录中含有同一个文件,则只有第一个被包含。
1、require函数和子程序库
用require函数可以创建可用于所有Perl程序的子程序库,步骤如下:
a、确定存贮子程序库的目录;
b、将子程序抽取放到单独的文件中,将文件放到子程序库目录;
c、每个文件末尾加一句非零值的语句,最简单的办法是语句 1;
d、在主程序中用require包含一个或多个所需的文件。
e、运行主程序时,用 -I 选项指定子程序库目录,或者在调用require前将该目录添加到@INC数组中。
例如:假设目录/u/perldir中存有你的Perl子程序库,子程序mysub存贮在文件mysub.pl中。现在来包含上该文件:
unshift (@INC, "/u/perldir");
require ("mysub.pl");
对unshift的调用把目录/u/perldir添加到@INC数组,对require的调用将mysub.pl文件的内容包含进来作为程序的一部分。
注意:
1.应该使用unshift来向@INC中添加目录,而不是push。因为push增加到@INC的末尾,则该目录将被最后搜寻。
2.如果你的库文件名与/usr/local/lib/perl中的某文件同名,则不会被包含进来,因为require只包含同名文件中的第一个。
3.用require指定Perl版本。
Perl 5中,可以用require语句来指定程序运行所需的Perl版本。当Perl解释器看到require后跟着数字时,则只有其版本高于或等于该数字时才运行该程序。例如,下面语句表明只有Perl解释器为5.001版或更高时才运行该程序:
require 5.001;
二、包
Perl程序把变量和子程序的名称存贮到符号表中,perl的符号表中名字的集合就称为包(package)。
1、包的定义
在一个程序中可以定义多个包,每个包有一个单独的符号表,定义语法为:
package mypack;
此语句定义一个名为mypack的包,从此以后定义的所有变量和子程序的名字都存贮在该包关联的符号表中,直到遇到另一个package语句为止。
每个符号表有其自己的一组变量、子程序名,各组名字是不相关的,因此可以在不同的包中使用相同的变量名,而代表的是不同的变量。如:
$var = 14;
package mypack;
$var = 6;
第一个语句创建变量$var并存贮在main符号表中,第三个语句创建另一个同名变量$var并存贮在mypack包的符号表中。
2、在包间切换
在程序里可以随时在包间来回切换,如:
1: #!/usr/bin/perl
2:
3: package pack1;
4: $var = 26;
5: package pack2;
6: $var = 34;
7: package pack1;
8: print ("$var\n");
运行结果如下:
$ program
26
$
第三行定义了包pack1,第四行创建变量$var,存贮在包pack1的符号表中,第五行定义新包pack2,第六行创建另一个变量$var,存贮在包pack2的符号表中。这样就有两个独立的$var,分别存贮在不同的包中。第七行又指定pack1为当前包,因为包pack1已经定义,这样所有变量和子程序的定义和调用都为该包的符号表中存贮的名字。因此第八行对$var的调用为pack1包中的$var,其值为26。
3、main包
存贮变量和子程序的名字的缺省符号表是与名为main的包相关联的。如果在程序里定义了其它的包,当你想切换回去使用缺省的符号表,可以重新指定main包:
package main;
这样,接下来的程序就好象从没定义过包一样,变量和子程序的名字象通常那样存贮。
4、包的引用
在一个包中可以引用其它包中的变量或子程序,方法是在变量名前面加上包名和一个单引号,如:
package mypack;
$var = 26;
package main;
print ("$mypack'var\n");
这里,$mypack'var为mypack包中的变量$var。
注意:在Perl 5中,包名和变量名用双冒号隔开,即$mypack::var。单引号引用的方式仍然支持,但将来的版本中未必支持。
5、指定无当前包
在Perl 5中,可以用如下语句指定无当前包:
package;
这时所有的变量必须明确指出所属包名,否则就无效--错误。
$mypack::var = 21; #ok
$var = 21; #error - no current package
这种情况直到用package语句指定当前包为止。
6、包和子程序
包的定义影响到程序中的所有语句,包括子程序,如:
package mypack;
subroutine mysub {
local ($myvar);
# stuff goes here
}
这里,mysub和myvar都是包mypack的一部分。在包mypack外调用子程序mysub,则要指定包:$mypack'mysub。
可以在子程序中切换包:
package pack1;
subroutine mysub {
$var1 = 1;
package pack2;
$var1 = 2;
}
这段代码创建了两个变量$var1,一个在包pack1中,一个在包pack2中,包中的局域变量只能在其定义的子程序等语句块中使用,像普通的局域变量一样。
7、用包定义私有数据
包最通常的用途是用在含有子程序和子程序所使用的全局变量的文件中,为子程序定义这样的包,可以保证子程序使用的全局变量不可在其它地方使用,这样的数据即为私有数据。更进一步,可以保证包名不可在其它地方使用。私有数据例:
1 : package privpack;
2 : $valtoprint = 46;
3 :
4 : package main;
5 : # This function is the link to the outside world.
6 : sub printval {
7 : &privpack'printval();
8 : }
9 :
10: package privpack;
11: sub printval {
12: print ("$valtoprint\n");
13: }
14:
15: package main;
16: # return value for require
此子程序只有在调用printval后才能产生输出。该文件分为两个部分:与外界联系的部分和私有部分。前者为缺省的main包,后者为包privpack。第6~8行定义的子程序printval 可被其它程序或子程序调用。printval输出变量$valtoprint的值,此变量仅在包privpack中定义和使用。第15、16行确保其被其它程序用require语句包含后工作正常,15行将当前包设置回缺省包main,16行返回非零值使require不报错。
8、包和系统变量
下列变量即使从其它包中调用,也在main包中起作用:
•文件变量STDIN, STDOUT, STDERR 和 ARGV
•变量%ENV, %INC, @INC, $ARGV 和 @ARGV
•其它含有特殊字符的系统变量
9、访问符号表
在程序中查找符号表可用数组%_package,此处package为想访问的符号表所属的包名。例如%_main含有缺省的符号表。通常不需要亲自查找符号表。
三、模块
多数大型程序都分割成多个部件,每一部件通常含有一个或多个子程序及相关的变量,执行特定的一个或多个任务。集合了变量和子程序的部件称为程序模块。
1、创建模块
Perl 5中用包来创建模块,方法是创建包并将之存在同名的文件中。例如,名为Mymodult的包存贮在文件Mymodult.pm中(扩展名.pm表示 Perl Module)。下例的模块Mymodult含有子程序myfunc1和myfunc2及变量$myvar1和$myvar2。
1 : #!/usr/local/bin/perl
2 :
3 : package Mymodule;
4 : require Exporter;
5 : @ISA = qw(Exporter);
6 : @EXPORT = qw(myfunc1 myfunc2);
7 : @EXPORT_OK = qw($myvar1 $myvar2);
8 :
9 : sub myfunc1 {
10: $myvar1 += 1;
11: }
12:
13: sub myfunc2 {
14: $myvar2 += 2;
15: }
第3~7行是标准的Perl模块定义方式。第3行定义包,第4行包含内置Perl模块Exporter,6、7行进行子程序和变量的输出以与外界联系。第6行创建名为@EXPORT的特殊数组,该数组中的子程序可以被其它程序调用,这里,myfunc1和myfunc2可以被访问。其它任何在模块中定义但没有赋给数组@EXPORT的子程序都是私有的,只能在模块内部调用。第7行创建另一个名为@EXPORT_OK的特殊数组,其中含有可被外部程序访问的变量,这里含有$myvar1和$myvar2。
2、导入模块
将模块导入你的Perl程序中使用use语句,如下句导入了Mymodule模块:
use Mymodule;
这样,模块Mymodule中的子程序和变量就可以使用了。取消导入模块使用no语句,如下句取消了Mymodule模块的导入:
no Mymodule;
下面看一个导入模块和取消导入的例子,使用integer模块要求所有数字运算基于整数,浮点数在运算前均被转化为整数。
1: #!/usr/local/bin/perl
2:
3: use integer;
4: $result = 2.4 + 2.4;
5: print ("$result\n");
6:
7: no integer;
8: $result = 2.4 + 2.4;
9: print ("$result\n");
程序输出如下:
$ program
4
4.8
如果use或no语句出现在语句块中,则只在该块的有效范围内起作用,如:
use integer;
$result1 = 2.4 + 2.4;
if ($result1 == 4) {
no integer;
$result2 = 3.4 + 3.4;
}
$result3 = 4.4 + 4.4;
结果输出如下:
4
6.8
8
这里,no语句只在if语句中有效,出了if语句仍使用integer模块,因此4.4在做加法前被转化成了4。
3、预定义模块
Perl 5提供了许多有用的预定义模块,可以用use导入和no语句取消。下面是库中最有用的一些模块:
integer 使用整数运算
Diagnostics 输出较多的诊断信息(警告)
English 允许英文名用作系统变量的别名
Env 导入环境变量的Perl模块
POSIX POSIX标准(IEEE 1003.1)的Perl接口
Socket 装载C语言的套接字处理机制
Perl文档中有完整的预定义模块列表。
注:世界各地的Perl 5用户写了许多有用的模块,CPAN(Comprehensive Perl Archive Network)的Perl文档有其完整的列表。
通过 Perl 分发自带的工具 h2xs 可以很简单的创建一个 Perl 模块,可以在命令行模式键入 h2xs 来看看它的参数列表。
h2xs 语法格式:
$ h2xs -AX -n ModuleName
参数说明:
-A 忽略 autoload 机制
-X 忽略 XS 元素
-n 指定扩展模块的名字
如果模块在 Person.pm 文件中,使用以下命令:
$ h2xs -AX -n Person
执行以上程序将输出:
Writing Person/lib/Person.pm
Writing Person/Makefile.PL
Writing Person/README
Writing Person/t/Person.t
Writing Person/Changes
Writing Person/MANIFEST
Person 目录下可以看到新增加的目录及文件说明:
README :这个文件包含一些安装信息,模块依赖性,版权信息等。
Changes :这个文件作为你的项目的修改日志(changelog)文件。
Makefile.PL :这是标准的 Perl Makefile 构造器。用于创建 Makefile.PL 文件来编译该模块。
MANIFEST :本文件用于自动构建 tar.gz 类型的模块版本分发。这样你就可以把你的模块拿到 CPAN 发布或者分发给其他人。它包含了你在这个项目中所有文件的列表。
Person.pm :这是主模块文件,包含你的 mod_perl 句柄代码(handler code)。
Person.t :针对该模块的一些测试脚本。默认情况下它只是检查模块的载入,你可以添加一些新的测试单元。
t/ :测试文件
lib/ :实际源码存放的目录
可以使用 tar (Linux 上) 命令来将以上目录打包为 Person.tar.gz。
四、小结
本节转自骏马金龙的博客空间,感谢原作者。
名称空间用于组织逻辑逻辑代码和数据,一个名称空间由一个包名,包内的所有子程序名以及包变量构成,出了这个名称空间就无法访问该名称空间内的内容,除非将其导入。有了包和名称空间,就可以避免名称冲突问题。包的名称由0个或多个双冒号分隔,以下都是有效的包名称:
File::Find::Rule
Module::Starter
DBIx::Class
Moose
aliased
File::Find和File模块没有关系,File::Find::Rule和File::Find模块也没有任何关系,最多可能的关系是一个作者开发的模块,方便区分,也可能不是同一个作者开发的,模块名都是完全独立的。模块名应尽量避免使用小写字母命名(例如上面的aliased模块),因为在使用use导入的时候,可能会被当作编译指示词。
对于包名My::Number::Utilities,一般来说,它对应的文件是My/Number/Utilities.pm,它通常位于lib目录下,即lib/My/Number/Utilities.pm。其中pm后缀文件表示一个perl模块,一个模块中可以有多个包(实际上,一个包也可以跨多个模块文件)。尽管如此,但强烈建议一个模块文件提供一个包,另外一个建议是模块文件名和包名称应该要保持一致,虽然这不是必须的。
(区分:模块和包。模块是文件,包是模块内的程序(假设包在一个模块内))
创建一个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子程序用于判断数字是否为质数(素数)。
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 VS. require
一般来说,当需要导入一个模块时可能会使用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()。本站也有同类文章《理解use_require_do使用方法》。
包变量
包变量有时候称为全局变量,虽然包自身是局部的,因为一个模块文件中可以定义多个包,在只有一个包的情况下,它们确实是等价的概念,但即使一个文件中多个包的情况下,包变量也是对所有外界可见的。
除了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;
范围声明our-my-local
访问和修改包变量
要访问某个包中的变量,可以使用完全限定名称$模块::变量。但可以在包中使用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:
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导出模块属性
定义好一个模块后,想要使用这个模块中的属性,可以使用完全限定名称的方式。但完全限定名称毕竟比较长,写起来比较麻烦,也比较容易出错。可以使用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 }