Perl包和模块(内容来自beginning perl)
单文件版的perl程序只能用于构建较小的脚本程序。当代码规模较大时,应该遵循下面两条规则来构建程序。这样能将程序的各个部分按功能一个一个地细化,便于维护,也便于后续开发。 能复用的代码放进函数 能复用的函数放进模块 名称空间和包名称空间用于组织逻辑逻辑代码和数据,一个名称空间由一个包名,包内的所有子程序名以及包变量构成,出了这个名称空间就无法访问该名称空间内的内容,除非将其导入。有了包和名称空间,就可以避免名称冲突问题。 包的名称由0个或多个双冒号分隔,以下都是有效的包名称:
模块名应尽量避免使用小写字母命名(例如上面的 对于包名 (区分:模块和包。模块是文件,包是模块内的程序(假设包在一个模块内)) 创建一个 $ 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,7919,7919 回到上面的模块文件Utilities.pm的代码部分,这里面包含了创建模块时的几个规范语句: package My::Number::Utilities; use strict; use warnings; our $VERSION = 0.01; # 设置模块版本号 ...这里是模块主代码... 1; 第一行是包名 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子程序可以通过 有时候想要限定包的作用域,只需将包放进一个代码块即可: 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; 你可能已经注意到了,模块文件的尾部总是使用 use VS. require一般来说,当你需要导入一个模块时,你可能会使用use语句: use My::Number::Utilities; use语句的用途很广,对于模块方面的功能来说,有以下几种相关操作: use VERSION use Module VERSION LIST use Module VERSION use Module LIST use Module 其中 use v5.8.1; use 5.8.1; use 5.008_001; 版本号前缀的"v"要求以3部分数值形式描述版本号(称为v-string),不建议使用这种描述形式,因为可能会出问题。 另外,当使用 对于这几种形式的use语句: use Module use Module LIST use Module VERSION use Module VERSION LIST 例如: use Test::More;
use Test::More 0.96; # 或 use Test::More v0.96.0; 当perl开始装载 强烈建议为每个模块设置版本号 our $VERSION = 0.01;
use Test::More tests => 13; perl会将列表[tests,13]作为参数传递给 如果只是装载模块,不想导入任何功能,可以传递一个空列表: use Test::More (); 最后,可以结合版本号和导入的参数列表: use Test::More 0.96 tests => 13; 除了使用use,还可以使用require导入模块(此外,eval、do都可以导入)。use语句是在编译器进行模块装载的,而require是在运行时导入模块文件的。 require My::Number::Utilities; 一般来说,除非必要,都只需使用use即可。但有时候为了延迟装载模块,可以使用require。例如,使用 sub debug { my @[email?protected]_; require Data::Dumper; Data::Dumper::Dumper(@args); } 这样,只有在某处失败,开始调用debug()的时候,才会导入这个模块,其他时候都不会触发该模块,因此必须使用全名 包变量包变量有时候称为全局变量,虽然包自身是局部的,因为一个模块文件中可以定义多个包,在只有一个包的情况下,它们确实是等价的概念,但即使一个文件中多个包的情况下,包变量也是对所有外界可见的。 除了my修饰的对象,所有属性、代码都独属于各自所在的包(如果没有声明包,则是默认的main包),所以通过包名称可以找到包中的内容(my不属于包,所以不能访问)。 可以使用完全限定名称或our来声明属于本包的包变量,甚至不加任何修饰符,但不加修饰符会被 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可以使用local和our两个修饰符修饰变量。以下是my、local、our的区别:
my和our的异同:
our和local的异同:
另一种理解my/local/our的区别:
当使用这3种修饰符时,如果只是声明没有赋值,my和local会将对象初始化为undef或空列表(),而our不会修改与之关联的全局变量的值(因为它操作的就是全局变量) 访问和修改包变量要访问某个包中的变量,可以使用完全限定名称 例如, 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; 如你所见,定义了几个包变量,但这里隐藏了一个问题: 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文件中从两个包访问 #!/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,$My::Number::Utilities::VERSION; # 输出:3 $My::Number::Utilities::VERSION=4; say "block2,$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()导入指定属性。 例如, 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 My::Number::Utilities 'pi','is_prime'; use My::Number::Utilities 'is_prime'; use My::Number::Utilities qw(pi is_prime); # 建议该方法 当其它程序导入模块的属性列表时,perl会调用 前面使用 our %EXPORT_TAGS = ( all => @EXPORT_OK ); # 按标签导出 这是一个hash结构,hash的key是标签名,value是要导出的属性列表的引用。上面导出的是all标签,其值是 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);
对于常量的导出,你可能会经常看到这样的声明方式: 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 } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |