分享好友 编程语言首页 频道列表

Perl包和模块(内容来自beginning perl)

perl  2023-02-09 10:270

单文件版的perl程序只能用于构建较小的脚本程序。当代码规模较大时,应该遵循下面两条规则来构建程序。这样能将程序的各个部分按功能一个一个地细化,便于维护,也便于后续开发。

能复用的代码放进函数
能复用的函数放进模块

名称空间和包

名称空间用于组织逻辑逻辑代码和数据,一个名称空间由一个包名,包内的所有子程序名以及包变量构成,出了这个名称空间就无法访问该名称空间内的内容,除非将其导入。有了包和名称空间,就可以避免名称冲突问题。

包的名称由0个或多个双冒号分隔,以下都是有效的包名称:

  • File::Find::Rule
  • Module::Starter
  • DBIx::Class
  • Moose
  • aliased

File::FindFile模块没有关系,File::Find::RuleFile::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()

包变量

包变量有时候称为全局变量,虽然包自身是局部的,因为一个模块文件中可以定义多个包,在只有一个包的情况下,它们确实是等价的概念,但即使一个文件中多个包的情况下,包变量也是对所有外界可见的。

除了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

可以使用local和our两个修饰符修饰变量。以下是my、local、our的区别:

  • my:将变量限定在一个代码块中。对于包范围内的my变量,它是包私有的,其它包无法访问。my实现的是词法作用域
  • our:声明一个词法作用域,但却引用一个全局变量。换句话说,our可以在给定作用域范围内操作全局变量,退出作用域后仍然有效。和my接近,都是词法作用域
    • 实际上是在词法作用域内定义一个和全局变量同名的词法变量,这个词法变量指向(引用)全局变量,所以修改这个词法变量的同时是在修改全局变量,退出作用域的时候,这个词法变量消失,但全局变量已经被修改了
  • local:临时操作全局变量,给全局变量赋值,退出作用域后消失,并恢复原始的全局变量值。local实现的是动态作用域
    • 除了local这个词语的意思和局部有关,在实际效果中和局部没任何关系。如果非要理解成局部的意思,可以认为local将全局变量暂时改变成了局部变量,在退出局部环境后又恢复全局变量
  • 如果要修饰除了标量、数组、hash外的其它内容,只能使用local,例如修饰文件句柄(local FH),修饰typeglob
  • 当使用这3种修饰符时,如果只是声明没有赋值,my和local会将对象初始化为undef或空列表(),而our不会修改与之关联的全局变量的值(因为它操作的就是全局变量)

Perl包和模块(内容来自beginning perl)

my和our的异同:

  • 同:都声明一个词法作用域,退出作用域时词法变量都消失
  • 同:都覆盖所有已同名的命令
  • 异:my声明的词法变量存放在临时范围暂存器中,和包独立
  • 异:our声明的词法变量,但操作的是全局变量(包变量)

our和local的异同:

  • 同:都操作全局变量
  • 异:our修改的全局变量在退出作用域后仍然有效
  • 异:local修改的全局变量在退出作用域后就恢复为之前的全局变量

另一种理解my/local/our的区别:

  • our confines names to a scope
  • local confines values to a scope
  • my confines both names and values to a scope

访问和修改包变量

要访问某个包中的变量,可以使用完全限定名称$模块::变量。但可以在包中使用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导出模块属性

定义好一个模块后,想要使用这个模块中的属性,可以使用完全限定名称的方式。但完全限定名称毕竟比较长,写起来比较麻烦,也比较容易出错。

可以使用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 }

查看更多关于【perl】的文章

展开全文
相关推荐
反对 0
举报 0
评论 0
图文资讯
热门推荐
优选好物
更多热点专题
更多推荐文章
Linux下安装Perl和Perl的DBI模块
今天在虚拟机测试shell脚本的时候,有些命令使用不了。比如说 mysqlhotcopy ,它提示Perl的版本太低。我用的 RedHat9 的Perl才5.8.0版本。。。(2002年以前的)严重过时。所以重新安装了新版本的 Perl,过程记录如下: 1、在官方网站下载新版本的源码包:http:

0评论2023-03-16464

Perl 与Form
说明事项: 這個範例用來說明如何經由網頁上的HTML form 表單元件來呼叫伺服器端的perl 程式。这个范例用来说明如何经由网页上的HTML form 表单元件来呼叫伺服器端的perl 程式。首先在網頁上設計表單元件,這個範例是設計一個按鈕,其原始碼如下:首先在网页

0评论2023-02-10545

Perl学习 perl培训
http://www.sun126.com/perl5/perl5-1.htm翻译: flamephoenix 第一章 概述一、Perl是什么?二、Perl在哪里?三、运行四、注释一、Perl是什么?  Perl是Practical Extraction and Report Language的缩写,它是由Larry Wall设计的,并由他不断更新和维护,用

0评论2023-02-10506

- calm_水手">Perl中的箭头符-> - calm_水手
Perl中的箭头符-2012-05-21 17:14 calm_水手 阅读(623) 评论(0) 编辑 收藏 举报  有两种用法,都和解引用有关。第一种用法,就是解引用。根据 - 后面跟的符号的不同,解不同类型的引用,-[] 表示解数组引用,-{} 表示解散列引用,-() 表示解子程序引

0评论2023-02-09731

perl脚本语言学习 perl脚本调用perl脚本
来公司的第二个星期便看了一下perl语言,发现掌握一门脚本语言还是非常有用的。到现在为止已经入职两个月,用perl脚本做了这些活:1. 修改了公司的一个爬取网页源代码的脚本2. 改进了一个出特征库的脚本,根据svn status的状态,来优化,将只需要添加的DB的数

0评论2023-02-09317

Perl模块的安装方法 perl 安装模块
1. 下载离线安装包 *.tar.gz的形式解包后,#perl Makefile.PL#make#make install2. 在联网的情况下,通过CPAN安装# perl -MCPAN -e shellcpan install PAR::Packer 

0评论2023-02-09909

Perl像C一样强大,像awk、sed等脚本描述语言一样方便。
Perl是由Larry Wall设计的,并由他不断更新和维护的编程语言。Perl具有高级语言(如C)的强大能力和灵活性。事实上,你将看到,它的许多特性是从C语言中借用来的。Perl与 脚本语言一样,Perl不需要编译器和链接器来运行代码,你要做的只是写出程序并告诉Perl

0评论2023-02-09370

27-Perl 进程管理
1.Perl 进程管理Perl 中你可以以不同的方法来创建进程。本教程将讨论一些进程的管理方法。你可以使用特殊变量 $$ 或 $PROCESS_ID 来获取进程 ID。%ENV 哈希存放了父进程,也就是shell中的环境变量,在Perl中可以修改这些变量。exit() 通常用于退出子进程,主

0评论2023-02-09436

在perl中简单的正则匹配 正则匹配或的使用
(一)、在perl中关于元字符的匹配元字符代表含义点号( .)匹配处换行符以外的任何单字符星号(*)匹配前面的内容零次或多次反斜线屏蔽元字符的特殊含义。\\代表\,\.匹配点号.*匹配所有的字符串加号(+)匹配前一个条目一次以上问号(?)表示前面一个条目可

0评论2023-02-09908

Perl WEB 开发之 Template
由于工作需要, 最近开始使用Perl来作为服务器脚本来处理Web 请求。系统采用的Template 来做Web page 的模板,用来简化繁琐但并不困难的HTML标签的编写。Question 1: Template Toolkit 是啥?Template Toolkit是一组Perl Module的集合, 它实现了一种快速的

0评论2023-02-09418

Perl到底是什么意思? perpol意思
学习perl也有一段时间了,如果连perl是什么意思都不知道,那就太汗颜了,听好啦!perl == Practical Exstraction and Report Language,中文叫做实用抽取和报表语言。

0评论2023-02-09437

perl-cgi-form2
代码:         #!/usr/local/bin/perl        use CGI ':standard';        print header;        print start_html("Example CGI.pm Form");        print "h1 Example CGI.pm Form/h1\n";      

0评论2023-02-09501

Perl实战(一) perl进阶
在Perl中,我们可以通过uc,lc,\U,\L来修改变量的值。其中uc,\U可以将变量中的字母全部转换为大写。              lc,\L可以将变量中的字母全部转换为小写。              $big = "\U$var";       $big = uc($var);  

0评论2023-02-09685

Perl多线程(2):数据共享和线程安全 多线程epoll
线程数据共享在介绍Perl解释器线程的时候一直强调,Perl解释器线程在被创建出来的时候,将从父线程中拷贝数据到子线程中,使得数据是线程私有的,并且数据是线程隔离的。如果真的想要在线程间共享数据,需要显式使用threads::shared模块来扩展threads模块的功

0评论2023-02-09683

Linux下安装与使用本地的perl模块 centos安装perl
在使用Linux或是unix时,perl是一个非常有用的脚本的语言。关于perl的模块安装,网上也有很多介绍,一方面可以通过不同套件自带的软件安装工具安装,一方面可以通过cpan安装,再者就是可以直接编译源代码。 这样,对于拥有root权限的用户来说,没有任何问题

0评论2023-02-09497

Perl_Tkx_Canvas绘图功能函数介绍
1.画画布:     $canvas = $mw-new_tk__canvas;2.画线:         $canvas-create_line(10,10,200,50,-fill=”red”,-width=3);配置item参数:       $canvas-itemconfigure($id, -fill = "blue", -width = 2);3.画椭圆         “ova

0评论2023-02-09658

perl: warning: Falling back to the standard locale ("C").
/********************************************************************************** *perl: warning: Falling back to the standard locale ("C"). * 说明: * 使用debootstrap的时候,遇到这个问题,记录解决方法。 **2017-2-18 深圳 南山平山村 曾剑锋

0评论2023-02-09574

Perl操作Mysql数据库 perl操作excel
一. 安装DBI模块步骤1:从TOOLS栏目中下载DBI.zip,下载完后用winzip解开到一个temp目录,共有三个文件:ReadmeDBI.ppdDBI.tar.gz步骤2: 在DOS窗口下,temp目录中运行下面的DOS命令:ppm install DBI.ppd 如果提示无效命令,可在perl/bin目录下运行 二. 安装DBD

0评论2023-02-09348

更多推荐