perlmod - Perl 模块(包和符号表)
还有其他文档可能包含您要查找的信息
Perl 的包、命名空间和一些关于类的信息。
关于创建新模块的教程。
创建新模块的最佳实践。
与 Perl 4 不同,Perl 4 中所有变量都是动态的,共享一个全局命名空间,导致可维护性问题,Perl 5 提供了两种机制来保护代码免受其他代码对其变量的破坏:使用 my
或 state
创建的词法作用域变量和命名空间全局变量,这些变量通过 vars
编译指示或 our
关键字公开。任何全局变量都被认为是命名空间的一部分,可以通过“完全限定形式”访问。相反,任何词法作用域变量都被认为是该词法作用域的一部分,并且没有“完全限定形式”。
在 perl 中,命名空间被称为“包”,package
声明告诉编译器将哪个命名空间作为前缀添加到 our
变量和未限定的动态名称。这既可以防止意外破坏,又可以提供一个接口,以便在需要时故意破坏在其他作用域或包中声明和使用的全局动态变量。
package
声明的作用域从声明本身到封闭块、eval
或文件的末尾,以先到者为准(与 my()、our()、state() 和 local() 运算符的作用域相同,也与实验性的“引用别名”的效果相同,这可能会改变),或者直到下一个 package
声明。未限定的动态标识符将在此命名空间中,除了以下描述的那些少数标识符,如果未限定,则默认为主包而不是当前包。package
语句仅影响动态全局符号,包括子程序名称和使用 local() 的变量,但不影响使用 my()、our() 或 state() 创建的词法变量。
通常,package
语句是通过 do
、require
或 use
运算符包含在程序中的文件中第一个声明。您可以在多个地方切换到一个包:package
除了指定编译器将使用哪个符号表来处理该块的其余部分或直到下一个 package
语句的动态符号之外,没有其他作用。您可以通过在标识符前加上包名和双冒号来引用其他包中的变量和文件句柄:$Package::Variable
。如果包名为空,则假定为 main
包。也就是说,$::sail
等效于 $main::sail
。
旧的包分隔符是单引号,但双冒号现在是首选的分隔符,部分原因是它对人类更易读,部分原因是它对 emacs 宏更易读。它还让 C++ 程序员感觉他们知道发生了什么——与使用单引号作为分隔符相反,单引号是为了让 Ada 程序员感觉他们知道发生了什么。由于旧的语法仍然为了向后兼容而得到支持,如果您尝试使用像 "This is $owner's house"
这样的字符串,您将访问 $owner::s
;也就是说,owner
包中的 $s 变量,这可能不是您的本意。使用大括号来消除歧义,例如 "This is ${owner}'s house"
。
使用 '
作为包分隔符已过时,将在 Perl 5.40 中删除。
包本身可以包含包分隔符,例如 $OUTER::INNER::var
。但是,这并不意味着名称查找的顺序。没有相对包:所有符号要么是当前包的本地符号,要么必须从外部包名称完全限定。例如,在包 OUTER
中,$INNER::var
不会引用 $OUTER::INNER::var
。INNER
指的是一个完全独立的全局包。将包名视为层次结构的习惯非常强烈,但语言本身并不强制执行。
只有以字母(或下划线)开头的标识符存储在包的符号表中。所有其他符号都保存在包 main
中,包括所有标点符号变量,例如 $_。此外,当未限定时,标识符 STDIN、STDOUT、STDERR、ARGV、ARGVOUT、ENV、INC 和 SIG 被强制在包 main
中,即使它们用于除内置用途之外的其他目的。如果您有一个名为 m
、s
或 y
的包,那么您不能使用标识符的限定形式,因为它将被解释为模式匹配、替换或转录。
以前,以下划线开头的变量被强制放入包 main,但我们认为,让包编写者能够使用前导下划线来表示私有变量和方法名更有用。但是,以单个 _
命名的变量和函数,例如 $_ 和 sub _
,仍然被强制放入包 main
。另请参阅 "perlvar 中的变量名称语法"。
eval
ed 字符串在编译 eval() 的包中编译。(但是,对 $SIG{}
的赋值假设指定的信号处理程序在 main
包中。如果您希望在包中使用信号处理程序,请限定信号处理程序名称。)例如,请检查 Perl 库中的 perldb.pl。它最初切换到 DB
包,以便调试器不会干扰您尝试调试的程序中的变量。但是,在某些情况下,它会暂时切换回 main
包以在 main
包(或您来自的任何地方)的上下文中评估各种表达式。请参阅 perldebug。
特殊符号 __PACKAGE__
包含当前包,但不能(轻松地)用于构造变量名。在 my($foo)
隐藏包变量 $foo
之后,仍然可以访问它,而无需知道您所在的包,例如 ${__PACKAGE__.'::foo'}
。
有关与 my() 和 local() 相关的其他作用域问题,请参阅 perlsub,有关闭包,请参阅 perlref。
包的符号表恰好存储在该名称的哈希中,并在其后附加了两个冒号。因此,主符号表的名称为%main::
,或简称为%::
。同样,前面提到的嵌套包的符号表名为%OUTER::INNER::
。
哈希中每个条目中的值是您在使用*name
类型全局符号表示法时所引用的内容。
local *main::foo = *main::bar;
例如,您可以使用它来打印出包中的所有变量。标准但过时的dumpvar.pl 库和 CPAN 模块 Devel::Symdump 利用了这一点。
直接创建新的符号表条目或修改任何不是类型全局符号的条目的结果是未定义的,并且可能会在 Perl 的不同版本之间发生变化。
对类型全局符号的赋值执行别名操作,即
*dick = *richard;
导致通过标识符richard
访问的变量、子例程、格式以及文件和目录句柄也可以通过标识符dick
访问。如果您只想为特定变量或子例程创建别名,请改为分配引用
*dick = \$richard;
这使得 $richard 和 $dick 成为同一个变量,但将 @richard 和 @dick 保留为单独的数组。很棘手,对吧?
以下语句之间存在一个细微的差别
*foo = *bar;
*foo = \$bar;
*foo = *bar
使类型全局符号本身成为同义词,而 *foo = \$bar
使两个不同类型全局符号的标量部分引用同一个标量值。这意味着以下代码
$bar = 1;
*foo = \$bar; # Make $foo an alias for $bar
{
local $bar = 2; # Restrict changes to block
print $foo; # Prints '1'!
}
将打印 '1',因为 $foo
持有对原始 $bar
的引用。那个被 local()
放置起来的,并在块结束时恢复。由于变量是通过类型全局符号访问的,因此您可以使用 *foo = *bar
创建一个可以本地化的别名。(但请注意,这意味着您不能拥有单独的 @foo
和 @bar
等。)
所有这些之所以重要,是因为 Exporter 模块使用全局符号别名作为导入/导出机制。您是否可以正确地本地化从模块中导出的变量取决于它是如何导出的
@EXPORT = qw($FOO); # Usual form, can't be localized
@EXPORT = qw(*FOO); # Can be localized
您可以通过使用完全限定名称 ($Package::FOO
) 来解决第一个情况,在需要本地值的地方使用它,或者通过在脚本中使用 *FOO = *Package::FOO
来覆盖它。
*x = \$y
机制可用于将廉价引用传递给子例程或从子例程返回,如果您不想复制整个内容。它仅在分配给动态变量时有效,而不是词法变量。
%some_hash = (); # can't be my()
*some_hash = fn( \%another_hash );
sub fn {
local *hashsym = shift;
# now use %hashsym normally, and you
# will affect the caller's %another_hash
my %nhash = (); # do what you want
return \%nhash;
}
返回时,引用将覆盖由 *some_hash 类型全局变量指定的符号表中的哈希槽。这是一种在您不想记住显式地取消引用变量时以廉价方式传递引用的有点棘手的方法。
符号表的另一个用途是创建“常量”标量。
*PI = \3.14159265358979;
现在您无法更改 $PI
,这总的来说可能是一件好事。这与常量子例程不同,常量子例程在编译时会进行优化。常量子例程是原型为不接受任何参数并返回常量表达式的子例程。有关这些的详细信息,请参阅 perlsub。use constant
编译指示是这些的便捷简写。
您可以使用 *foo{PACKAGE}
和 *foo{NAME}
来找出 *foo 符号表条目来自哪个名称和包。这在将类型全局变量作为参数传递给子例程时可能很有用。
sub identify_typeglob {
my $glob = shift;
print 'You gave me ', *{$glob}{PACKAGE},
'::', *{$glob}{NAME}, "\n";
}
identify_typeglob *foo;
identify_typeglob *bar::baz;
这将打印
You gave me main::foo
You gave me bar::baz
*foo{THING}
符号也可以用于获取对 *foo 的各个元素的引用。请参阅 perlref。
子例程定义(以及声明,就此而言)不必一定位于其符号表所在的包中。您可以通过显式限定子例程的名称来在包外部定义子例程。
package main;
sub Some_package::foo { ... } # &foo defined in Some_package
这只是编译时类型全局变量赋值的简写。
BEGIN { *Some_package::foo = sub { ... } }
并且不与编写以下内容相同。
{
package Some_package;
sub foo { ... }
}
在前两个版本中,子例程的主体在词法上位于主包中,不在 Some_package 中。因此,像这样
package main;
$Some_package::name = "fred";
$main::name = "barney";
sub Some_package::foo {
print "in ", __PACKAGE__, ": \$name is '$name'\n";
}
Some_package::foo();
打印
in main: $name is 'barney'
而不是
in Some_package: $name is 'fred'
这也对 SUPER:: 限定符的使用有影响(请参阅 perlobj)。
在运行的 Perl 程序开始和结束时,会执行五个特殊命名的代码块。它们分别是 BEGIN
、UNITCHECK
、CHECK
、INIT
和 END
代码块。
这些代码块可以加上 sub
前缀,使其看起来像子程序(虽然这并不被认为是好的风格)。需要注意的是,这些代码块实际上并不存在作为命名的子程序(尽管它们看起来像)。揭示这一点的是,你可以在一个程序中拥有 **多个** 这样的代码块,并且它们将在适当的时候 **全部** 执行。因此,你无法通过名称执行任何这些代码块。
BEGIN
代码块在尽可能早的时候执行,也就是说,在它完全定义的那一刻执行,甚至在解析包含它的文件(或字符串)的其余部分之前。你可以在一个文件中(或 eval
的字符串中)拥有多个 BEGIN
代码块;它们将按照定义的顺序执行。由于 BEGIN
代码块立即执行,它可以及时从其他文件中拉取子程序等的定义,以便在编译和运行时对其余部分可见。一旦 BEGIN
运行完毕,它就会立即被取消定义,并且它使用的任何代码都会被返回到 Perl 的内存池中。
END
代码块在尽可能晚的时候执行,也就是说,在 Perl 完成程序运行并即将退出解释器时执行,即使它是由于 die()
函数而退出。(但如果它通过 exec
转换为另一个程序,或者被信号吹走,则不会执行 - 你必须自己捕获它(如果可以)。)你可以在一个文件中拥有多个 END
代码块 - 它们将按照定义的逆序执行;也就是说:后进先出 (LIFO)。当你使用 -c
开关运行 Perl 或编译失败时,END
代码块不会执行。
请注意,END
代码块 **不会** 在字符串 eval()
的末尾执行:如果在字符串 eval()
中创建了任何 END
代码块,它们将在解释器即将退出时,与该包中的任何其他 END
代码块一样,按照 LIFO 顺序执行。
在 END
代码块中,$?
包含程序将传递给 exit()
的值。你可以修改 $?
来更改程序的退出值。注意不要意外地更改 $?
(例如,通过 system
运行某些东西)。
在 END
代码块内部,${^GLOBAL_PHASE}
的值为 "END"
。
与 END
代码块类似的是 defer
代码块,但它们作用于单个代码块作用域的生命周期,而不是整个程序。它们在 "defer" in perlsyn 中有说明。
UNITCHECK
、CHECK
和 INIT
代码块有助于捕获主程序的编译阶段和执行阶段之间的转换。
UNITCHECK
代码块在定义它们的单元编译完成后立即运行。主程序文件和它加载的每个模块都是编译单元,字符串 eval
、使用正则表达式中的 (?{ })
结构编译的运行时代码、对 do FILE
、require FILE
的调用以及命令行上 -e
开关后的代码也是如此。
BEGIN
和 UNITCHECK
代码块与解释器的阶段没有直接关系。它们可以在任何阶段创建和执行。
CHECK
代码块在 Perl 初始编译阶段结束后和运行时开始之前以 LIFO 顺序运行。CHECK
代码块用于 Perl 编译器套件中保存程序的编译状态。
在 CHECK
代码块内部,${^GLOBAL_PHASE}
的值为 "CHECK"
。
INIT
代码块在 Perl 运行时开始执行之前以“先进先出”(FIFO)顺序运行。
在 INIT
代码块内部,${^GLOBAL_PHASE}
的值为 "INIT"
。
如果 require
、字符串 do
或字符串 eval
编译的代码中的 CHECK
和 INIT
代码块出现在主编译阶段结束之后,它们将不会被执行;这在 mod_perl 和其他使用这些函数在运行时加载代码的持久环境中可能是一个问题。
当您对 Perl 使用 -n 和 -p 开关时,BEGIN
和 END
的工作方式与 awk 中一样,作为一种退化情况。当您对 Perl 使用 -c 开关进行仅编译语法检查时,BEGIN
和 CHECK
代码块都会运行,尽管您的主代码不会运行。
begincheck 程序最终会让一切变得清晰
#!/usr/bin/perl
# begincheck
print "10. Ordinary code runs at runtime.\n";
END { print "16. So this is the end of the tale.\n" }
INIT { print " 7. INIT blocks run FIFO just before runtime.\n" }
UNITCHECK {
print " 4. And therefore before any CHECK blocks.\n"
}
CHECK { print " 6. So this is the sixth line.\n" }
print "11. It runs in order, of course.\n";
BEGIN { print " 1. BEGIN blocks run FIFO during compilation.\n" }
END { print "15. Read perlmod for the rest of the story.\n" }
CHECK { print " 5. CHECK blocks run LIFO after all compilation.\n" }
INIT { print " 8. Run this again, using Perl's -c switch.\n" }
print "12. This is anti-obfuscated code.\n";
END { print "14. END blocks run LIFO at quitting time.\n" }
BEGIN { print " 2. So this line comes out second.\n" }
UNITCHECK {
print " 3. UNITCHECK blocks run LIFO after each file is compiled.\n"
}
INIT { print " 9. You'll see the difference right away.\n" }
print "13. It only _looks_ like it should be confusing.\n";
__END__
Perl 中没有特殊的类语法,但如果一个包提供了用作方法的子例程,它就可以充当一个类。这样的包也可以从另一个类(包)继承一些方法,方法是在其全局 @ISA 数组中列出另一个包的名称(该数组必须是包全局的,而不是词法范围的)。
有关更多信息,请参阅 perlootut 和 perlobj。
模块只是一组在库文件中的相关函数,即与文件同名的 Perl 包。它专门设计为可供其他模块或程序重复使用。它可以通过提供一种机制将一些符号导出到使用它的任何包的符号表中来实现这一点,或者它可以充当类定义,并通过对类及其对象的调用隐式地提供其语义,而无需显式导出任何内容。或者它可以同时做这两件事。
例如,要启动一个传统的非面向对象的模块,名为 Some::Module,创建一个名为 Some/Module.pm 的文件,并从以下模板开始
package Some::Module; # assumes Some/Module.pm
use v5.36;
# Get the import method from Exporter to export functions and
# variables
use Exporter 5.57 'import';
# set the version for version checking
our $VERSION = '1.00';
# Functions and variables which are exported by default
our @EXPORT = qw(func1 func2);
# Functions and variables which can be optionally exported
our @EXPORT_OK = qw($Var1 %Hashit func3);
# exported package globals go here
our $Var1 = '';
our %Hashit = ();
# non-exported package globals go here
# (they are still accessible as $Some::Module::stuff)
our @more = ();
our $stuff = '';
# file-private lexicals go here, before any functions which use them
my $priv_var = '';
my %secret_hash = ();
# here's a file-private function as a closure,
# callable as $priv_func->();
my $priv_func = sub {
...
};
# make all your functions, whether exported or not;
# remember to put something interesting in the {} stubs
sub func1 { ... }
sub func2 { ... }
# this one isn't always exported, but could be called directly
# as Some::Module::func3()
sub func3 { ... }
END { ... } # module clean-up code here (global destructor)
1; # don't forget to return a true value from the file
然后继续在函数中声明和使用变量,而无需任何限定符。有关模块创建中的机制和样式问题,请参阅 Exporter 和 perlmodlib。
通过以下语句将 Perl 模块包含到您的程序中
use Module;
或
use Module LIST;
这与以下语句完全等效
BEGIN { require 'Module.pm'; 'Module'->import; }
或
BEGIN { require 'Module.pm'; 'Module'->import( LIST ); }
作为特殊情况
use Module ();
与以下语句完全等效
BEGIN { require 'Module.pm'; }
所有 Perl 模块文件都具有扩展名 .pm。use
运算符假设这一点,因此您不必在引号中拼写出 "Module.pm"。这也有助于将新模块与旧的 .pl 和 .ph 文件区分开来。模块名称也使用大写字母,除非它们充当编译指示;编译指示实际上是编译器指令,有时被称为“实用模块”(或者如果您是古典主义者,则被称为“实用程序”)。
以下两个语句
require SomeModule;
require "SomeModule.pm";
在两个方面有所不同。在第一种情况下,模块名称中的任何双冒号(例如 Some::Module
)都会转换为您的系统目录分隔符,通常为 "/”。第二种情况不会,必须按字面意思指定。另一个区别是,看到第一个 require
会提示编译器,使用涉及 "SomeModule" 的间接对象表示法,例如 $ob = purge SomeModule
,是方法调用,而不是函数调用。(是的,这确实会产生影响。)
由于 `use` 语句隐含了一个 `BEGIN` 块,因此语义的导入在 `use` 语句被编译时立即发生,早于文件其余部分的编译。这就是它能够作为一种编译指示机制,以及模块能够声明子例程,这些子例程随后在当前文件的其余部分可见,作为列表或一元运算符。如果您使用 `require` 而不是 `use`,这将不起作用。使用 `require`,您可能会遇到这个问题。
require Cwd; # make Cwd:: accessible
$here = Cwd::getcwd();
use Cwd; # import names from Cwd::
$here = getcwd();
require Cwd; # make Cwd:: accessible
$here = getcwd(); # oops! no main::getcwd()
通常,`use Module ()` 比 `require Module` 更推荐,因为它在编译时确定模块的可用性,而不是在程序执行的中间。一个例外是,如果两个模块都尝试 `use` 彼此,并且每个模块都调用了来自另一个模块的函数。在这种情况下,使用 `require` 代替会更容易。
Perl 包可以嵌套在其他包名称中,因此我们可以拥有包含 `::` 的包名称。但是,如果我们直接将该包名称用作文件名,它会在某些系统上造成笨拙或不可能的文件名。因此,如果一个模块的名称是,例如,`Text::Soundex`,那么它的定义实际上是在库文件 `Text/Soundex.pm` 中找到的。
Perl 模块始终具有 `*.pm` 文件,但可能还存在与模块关联的动态链接可执行文件(通常以 `*.so` 结尾)或自动加载的子例程定义(通常以 `*.al` 结尾)。如果是这样,这些对于模块的用户来说将是完全透明的。`*.pm` 文件有责任加载(或安排自动加载)任何额外的功能。例如,虽然 POSIX 模块恰好同时执行动态加载和自动加载,但用户只需说 `use POSIX` 就可以获得所有功能。
Perl 支持一种称为解释器线程(ithreads)的线程。这些线程可以显式和隐式使用。
Ithreads 通过克隆数据树来工作,这样不同线程之间就不会共享数据。这些线程可以通过使用 `threads` 模块或在 win32 上执行 fork()(伪造 fork() 支持)来使用。当克隆一个线程时,所有 Perl 数据都会被克隆,但是非 Perl 数据不能自动克隆。5.8.0 之后的 Perl 支持 `CLONE` 特殊子例程。在 `CLONE` 中,您可以执行任何需要执行的操作,例如,如果需要,处理非 Perl 数据的克隆。`CLONE` 将作为类方法为每个定义了它(或继承了它)的包调用一次。它将在新线程的上下文中被调用,因此所有修改都在新区域进行。目前,`CLONE` 在调用时没有其他参数,只有调用者包名,但代码不应该假设这将保持不变,因为将来可能会传递额外的参数以提供有关克隆状态的更多信息。
如果你想克隆所有对象,你需要按包跟踪它们。这可以通过使用哈希和 Scalar::Util::weaken() 来轻松实现。
Perl 5.8.7 之后版本支持 CLONE_SKIP
特殊子例程。与 CLONE
一样,CLONE_SKIP
针对每个包调用一次;但是,它在克隆开始之前调用,并且在父线程的上下文中调用。如果它返回一个真值,那么该类的任何对象都不会被克隆;或者更确切地说,它们将被复制为未祝福的、未定义的值。例如:如果在父线程中,有两个引用指向同一个祝福的哈希,那么在子线程中,将有两个引用指向同一个未定义的标量值。这提供了一种简单的机制来使模块线程安全;只需在类的顶部添加 sub CLONE_SKIP { 1 }
,DESTROY()
现在将只针对每个对象调用一次。当然,如果子线程需要使用这些对象,那么需要更复杂的方法。
与 CLONE
一样,CLONE_SKIP
目前调用时没有参数,除了调用者包名,尽管这可能会改变。同样,为了允许将来扩展,返回值应该是一个单独的 0
或 1
值。
参见 perlmodlib 以了解与构建 Perl 模块和类相关的通用样式问题,以及对标准库和 CPAN 的描述,Exporter 以了解 Perl 的标准导入/导出机制的工作原理,perlootut 和 perlobj 以了解有关创建类的深入信息,perlobj 以了解有关对象的硬核参考文档,perlsub 以了解有关函数和作用域的解释,以及 perlxstut 和 perlguts 以了解有关编写扩展模块的更多信息。