加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

如何编写简单的Perl模块

发布时间:2020-12-15 23:58:51 所属栏目:大数据 来源:网络整理
导读:为什么要写或要模块呢?简言之:代码重用,更多见于写一组工具集,有很多地方是可以避免重写的。如何能把这些独立抽象出来,在需要的时候载入进程序执行,提高编写的效率和减少维护。基于此,我们需要Perl模块,而如何使用这些模块,请参考“理解use_require

为什么要写或要模块呢?简言之:代码重用,更多见于写一组工具集,有很多地方是可以避免重写的。如何能把这些独立抽象出来,在需要的时候载入进程序执行,提高编写的效率和减少维护。基于此,我们需要Perl模块,而如何使用这些模块,请参考“理解use_require_do使用方法”

需要知道的一些基础知识:
package,main,require,use,Exporter,EXPORT,EXPORT_OK,EXPORT_TAG,了解@INC,%INC,do 这些概念,用法和区别。

package
包的概念在Perl还是比较简单的。我们可以理解为文件夹。然后再文件夹里面是一堆我们的东东,这个那个的。要找什么,去包里找吧。这是通俗易懂的理解。
学术一点就是,Perl将变量,子程序都存贮到一个符号表中,Perl符号表中名字的集合就是package。

通过几行代码后,就更能理解了。来看看下面的代码:

Greeting.pl?
#!/bin/env?perl?
use?strict;?
use?warnings;?
use?Carp?qw(carp);?
sub?sayHello{?carp"Hellon";}?
sub?sayHi{?carp?"Hin";}


程序非常简单,两个方法,一个说Hello,一个说Hi,其他的什么都没有。我在这里用Carp模块的carp函数替代print以便打印出更多程序的跟踪信息,这样可以让我们更好的理解,当您运行后什么都不会看到。让我们再写一个程序来调用它看看。
Greeting_test.pl

#!/bin/env?perl?
use?strict;?
use?warnings;?
require?"Greeting.pl";?
sayHello();


运行Greeting_test.pl之后,你可能希望输出Hello。但是屏幕输出

$ ./Greeting_test.pl
Greeting.pl did not return a true value at ./Greeting_test.pl line 5.

信息提示一目了然,Greeting.pl没有返回True。require没有能够成功加载Greeting.pl,程序报错退出。
虽然是退出了,但是不代表require没能成功编译文件,如果大家对require加载的步骤了解的话,就知道原因了。我们修改一下Greeting_test.pl

#!/bin/env?perl?
use?strict;?
use?warnings;?
eval{?require?"Greeting.pl"?};?
sayHello();


用eval捕获require遇到的错误,这样程序还是可以继续运行,结果是
Hello
?at Greeting.pl line 5
? main::sayHello() called at Greeting_req.pl line 5

Hello还是被成功打印出来了,后面的语句只是用来跟踪程序的。用eval来绕过require带来的异常错误的,报错没有被打印出来,它保存在$@中。只是为了可以更好理解require所作的工作,通常情况下,我们会让被调用的文件或模块强制返回一个True。传统上的做法是在程序的最后一行加入”1;”。现在Greeting.pl变成了:

#!/bin/env?perl?
use?strict;?
use?warnings;?
use?Carp?qw(carp);?
sub?sayHello{?carp"Hellon";}?
sub?sayHi{?carp?"Hin";}?
1;


现在我们可以去掉Greeting_test.pl中的eval了,程序会返回同样的结果。
Hello
?at Greeting.pl line 5
? main::sayHello() called at Greeting_req.pl line 5

让我们来看看第三行的内容,main::sayHello() ,这里的意思是main::sayHello() 在 Greeting_test.pl 中的第5行被调用了,那么main::是什么呢,这个就是package。所有未明确指定package的变量,子程序都会被存放到main这个包里去。 现在是讨论模块的时候,我不喜欢pl结为,我把他改成更像是Perl模块的文件,以pm结尾,然后再在Greeting_test.pl中作相应的修改,顺便把require换成use,这样做,便于之后可以少写几行代码。 最后我们在Greeting.pm指定模块名为Greeting。最后的代码是:

Greeting.pm?
#!/bin/env?perl?
package?Greeting;?
use?strict;?
use?warnings;?
use?Carp?qw(carp);?
sub?sayHello{?carp?"Hellon";?}?
sub?sayHi{?carp?"Hin";?}?
1;


Greeting_test.pl

#!/usr/bin/perl?
use?strict;?
use?warnings;?
use?Greeting;?
sayHello();


现在让我们运行一下Greeting_test.pl.
结果返回
$ ./Greeting_test.pl
Undefined subroutine &main::sayHello called at ./Greeting_test.pl line 6.

错误报告显示未定义的子程序&main::sayHello。这正是由于我们在Greeting.pm中改变了包名,所以程序将Greeting.pm中的所有变量以及子程序名都存储在了位于Greeting包的符号表中。这样我们访问sayHello子程序的方法就要通过包名::子程序名,这样的方式访问了。如果不指定包名,perl会main包的符号表中找sayHello,当然我们知道sayHello已经不在那里了。现在我们通过包名::子程序名,这样的方式访问:
Greeting::sayHello();

嗯,我们又看到了Hello出现在屏幕上。这样的访问方式实在是太繁琐,为什么一定要使用包呢?包能很好的解决了变量,子程序名重名的问题。试想一下,如果没有指定包名那么,如果在你的Perl程序与模块里的变量与子程序名同名会怎样。
Greeting_test.pl


让我们在Greeting.pm中注释package Greeting;这一行,然后运行
$ ./Greeting_test.pl
Hello from Greeting_test
at ./Greeting_test.pl line 6
main::sayHello() called at ./Greeting_test.pl line 7

模块的sayHello被屏蔽了。这样的结果或许是你想要的,或者是你不想看到的,重要的是,我们失去了模块的sayHello子程序包的概念应该已经很清楚了。

-----
Exporter
现在我们也许希望可以在模块中指定包名,又希望通过直接调用子程序名的方式来执行。嗯,这样的需求很合理。让我们在Greeting.pm中加点什么。如果大家已经了解require和use的区别的话,就一定知道使用require时我们可以直接调用import这个子程序来导出我们呢所需要的子程序名或者变量。
require Greeting;
Greeting->import(qw(sayHello sayHi));

Perl中的符号表是可以操作的,符号表是一个类似于哈希的结构,但他不是一个完整的哈希,他不具备所有哈希的特性以及所有可能的对于哈希进行操作的函数,不过我们可将其看作为一个哈希。可以思考一下,其实我们在import子程序中,将所有不同包中的变量以及子程序名的应用加入到main包的符号表哈希中,不就可以直接使用了嘛,的却就是这样简单。
Greeting.pm

#!/bin/env?perl?
package?Greeting;?
use?strict;?
use?warnings;?
use?Carp?qw(carp);?
??
sub?import{?
?no?strict?'refs';?
?foreach?(@_)?{?
??*{"main::$_"}=&;$_;?
?}?
}?
sub?sayHello{?carp?"Hellon";?}?
sub?sayHi{?carp?"Hin";?}


Greeting_test.pl
#!/usr/bin/perl
use strict;
use warnings;
use Greeting qw(sayHello);
use Carp qw(carp);
?
sayHello();

use Greeting qw(sayHello)
相当于调用了Greeting.pm的import方法并将sayHello作为参数传入了Greeting模块的import的子程序。在import的子程序中所作的操作就是之前所说将Greeting包中的变量以及子程序名的应用加入到main包的符号表哈希中。现在sayHello又可以像先前一样正常工作了,但如果我们不在main包中,而是在其他包中调用呢,这样的方法就显得有太多的限制了。庆幸的是,现在有一个标准的import子程序在Exporter模块中,我们要做的仅仅是加入一行:
use Exporter;

这样,import子程序就能够导出方法给调用者所在的包了。

@EXPORT
Exporter模块中的import会检查列表@EXPORT来确定哪些变量已经子程序在默认状态下被导出,例如在Greeting模块里:
package Greeting;
our @EXPORT=qw(sayHello);
use Exporter;

这就表示在默认情况下sayHello也会被导出如果我们不指定import的列表时。

use Greeting;
BEGIN { require Greeting; Greeting->import }

以上两行的代码,效果是一样的。虽然import list没有被提供,但是sayHello也会被导入进来。这里的import后面没有导入列表,这个导入一个空列表是不一样的,导入一个空列表代表模块的import子程序不会被调用。

@EXPORT_OK
假如我们希望一些子程序或者变量不被默认的导入,同时他们可以在需要时被导入。这样我们可以把把这些子程序或者变量加入到名为@EXPORT_OK的列表中。
package Greeting;
our @EXPORT=qw(sayHello);
our @EXPORT_OK=qw(sayHi);
use Exporter;

use Greeting qw(sayHello sayHi);
这样sayHello和sayHi现在都被导入了。但是如果我们这样写:
use Greeting qw(sayHi);

虽然sayHello出现在@EXPORT列表中代表默认导入,但是如果在我们指定了import list的话,默认的@EXPORT会被忽略。所以,在这种情况下,我们不再能够直接对sayHello进行调用。总而言之,任何被在import list被指定导入的变量或者子程序必须出现在@EXPORT或者@EXPORT_OK中。

现在我们似乎可以对所导入的内容进行了限制,但是这样的方法不会阻止类似于包名::变量名(子程序名)这样的调用。但如果出现这样的情况,我们至少就能知道那些不希望被调用子程序或者变量正在被调用。

%EXPORT_TAGS
实际上,我们没有必要一一指定那些需要被导入的变量已经子程序,这里是有快捷方式的。那些我们需要被导入的变量和子程序可以被组合起来,然后打上标签。这样只要标签被导入,标签下的变量以及子程序就同样被导入了。导入标签的形式是:
use Greeting qw(:DEFAULT);

用一个冒号后面紧紧跟着的就是标签名。这样子就导入了位于DEFAULT标签下的所有变量和子程序。嗯,这个似乎没有什么用处吗,要导入默认的话,直接全部添加进@EXPORT列表里就好了,何必再创建一个标签呢?当然我们还可以这样用:
use Greeting qw(:DEFAULT sayHi);

这样有用多了,DEFAULT标签下的所有东东和sayHi子程序一起被导入了。

类似这样的导入方法在实际应用当中很少能够看见。原因是,提供一个导入列表的目的是能够在程序中实现对模块子程序使用的控制。但是对同一个模块,我们可能会修改一些原来的实现方法,又或者是添加了一些新的功能和变量。那么对于一个有着很多子程序或变量的模块来说,这些修改或者添加都可能导致与调用该模块的程序产生冲突而不能正常工作。标签的方法并不能将我们的程序从这种潜在的危险中隔离开来。在Perl中,我们有着其他的一些方法来提供更细节的控制。这里我们以后再讨论。

现在让我们看看如何在模块中定义TAGS

#!/bin/env?perl?
package?Greeting;?
use?strict;?
use?warnings;?
use?Carp?qw(carp);?
use?Exporter;?
??
our?@EXPORT=qw(sayHello);?
our?@EXPORT_OK=qw(sayHi);?
our?%EXPORT_TAGS=(?
????all?=>?[@EXPORT,@EXPORT_OK],?
????sayHi?=>?[qw(sayHi)],?
);?
sub?sayHello{?carp?"Hellon";?}?
sub?sayHi{?carp?"Hin";?}?
1;


EXPORT_TAGS是一个哈希,我们只需要将包含变量或者子程序名的匿名数组赋值给以标签名为键的哈希并可以创建我们上面所提到的快捷方式了。

总结

模块调用风格:传统的和面向对象的.
传统风格的模块:为调用者导入和使用定义的子例程和变量
面象对象风格的模块:以类来调用

所以模块有二种方法来为它的接口提供给程序来使用:导出符号,要不就是允许方法调用。
?
模块加载
编译时时行一次预装载,然后给需要的符号输入进来,这样编译后,在运行时就可以使用这些模块中的符号(子例程和变量)。

注意:如果没有提供所需要的符号LIST(列表),那么就使用模块中内部的@EXPORT数组中命名的符号;如果提供了这个列表,还需要在模块本身有的 @EXPORT要不是在@EXPORT_OK数组中提取。
?
模块名字
建议名字第一个字母大写,除非其是pragma的作用。

require和use的分别
请参考:理解use_require_do使用方法

基本模块样例

package?Freeoa;?
require?Exporter;?
??
our?@ISA?=?qw(Exporter);?
our?@EXPORT?=?qw(camel);?#默认导出的符号?
our?@EXPORT_OK?=?qw($weight);?#按要求导出的符号?
our?%EXPORT_TAGS?=?(camel?=>?[qw(camel?$weight)]);?#导出时的符号组?
our?$VERSION?=?1.00;?
??
sub?camel?{print??"TEST"};?
$weight?=?1024;?
1;

@EXPORT数组包含默认导出的变量和函数的名字,当use packagename时就会得到的东西,@EXPORT_OK中的变量和函数只有当程序中use语句中特别要求时才会导出。最 后%EXPORT_TAGS中的键值对允许程序包含那些在@EXPORT和@EXPORT_OK中列出的特定的符号组,如果不想外面的模块导出什么,可以 使用@EXPORT_FAIL来实现。 符号组因为一定需要出现在@EXPORT和@EXPORT_OK中,因此perl提供了二个函数来处理 %EXPORTER_TAGS = (foo => [qw(aa bb cc)],bar => [qw(aa cc dd)]); Exporter::export_tags('foo'); #给aa,bb和cc导入到@EXPORT Exporter::export_ok_tags('bar'); #给aa,cc和dd导入到@EXPORT_OK

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读