Skip to content

Perl 包与模块

包(Packages)和模块(modules)是 Perl 设计的基础,它们支持代码组织、可重用性以及命名空间管理 (namespacing)。

包是 Perl 中一个独立的命名空间。它允许你将相关的变量和子例程分组,防止与程序其他部分或不同模块中的代码发生命名冲突。

  • 包本质上是位于其自己的符号表 (symbol table)(命名空间)中的代码集合。
  • 命名空间确保一个包中的变量(例如,$name)不会与另一个包中同名的变量冲突。
  • 包对于构建模块至关重要,模块是自包含的可重用代码单元。
  • package 声明将当前编译上下文切换到指定的命名空间。此效果持续到遇到另一个 package 语句,或直到当前代码块或文件结束。
  • 你可以使用 :: 限定符访问其他包中的变量或子例程(例如,OtherPackage::some_function(),$OtherPackage::some_variable)。

这是一个在单个文件中演示不同包的示例。特殊变量 __PACKAGE__ 保存当前包的名称。

#!/usr/bin/env perl
use strict;
use warnings;
# 这是默认的 'main' 包
our $i = 1; # 包变量
print "In package: ", __PACKAGE__, ", \$i = $i\n";
package Foo;
# 现在我们在 'Foo' 包中
our $i = 10; # 另一个 $i,作用域限定在 Foo
print "In package: ", __PACKAGE__, ", \$i = $i\n";
sub bar {
print "Function bar in package Foo reporting: ", __PACKAGE__, "\n";
}
package Bar;
# 现在我们在 'Bar' 包中
our $i = 20;
print "In package: ", __PACKAGE__, ", \$i = $i\n";
Foo::bar(); # 调用另一个包中的子例程
package main;
# 切换回 'main' 包
our $i = 100;
print "In package: ", __PACKAGE__, ", \$i = $i\n";
print "Accessing Foo's \$i from main: $Foo::i\n";
print "Accessing Bar's \$i from main: $Bar::i\n";
1; # 模块和包通常以真值结束

执行上述代码后,会生成以下输出:

In package: main, $i = 1
In package: Foo, $i = 10
In package: Bar, $i = 20
Function bar in package Foo reporting: Foo
In package: main, $i = 100
Accessing Foo's $i from main: 10
Accessing Bar's $i from main: 20

BEGIN 和 END 代码块 (BEGIN and END Blocks)

Section titled “BEGIN 和 END 代码块 (BEGIN and END Blocks)”

Perl 允许定义名为 BEGIN 和 END 的特殊代码块。

BEGIN {
# 此处的代码一旦编译就会运行,
# 在脚本其余部分开始之前。
}
END {
# 此处的代码在 Perl 解释器退出时运行,
# 在主脚本执行完成或调用 die_handler 之后。
}
  • BEGIN 代码块在完全编译后立即执行,甚至在文件其余部分被解析之前。多个 BEGIN 代码块按照它们出现的顺序执行。
  • END 代码块按照它们出现的逆序执行,就在解释器退出之前。即使脚本因未捕获的 die 而退出,它们也会运行。
  • 这些代码块在模块中特别有用,用于执行设置 (BEGIN) 或清理 (END) 任务。

BEGIN 和 END 代码块示例:

#!/usr/bin/env perl
use strict;
use warnings;
package MyDemo;
print "Main script execution starts here.\n"; # 主脚本执行从这里开始
BEGIN {
print "This is inside a BEGIN block. It runs first!\n"; # 这是在一个 BEGIN 代码块内。它首先运行!
}
END {
print "This is inside an END block. It runs last!\n"; # 这是在一个 END 代码块内。它最后运行!
}
BEGIN {
print "This is another BEGIN block. It also runs early!\n"; # 这是另一个 BEGIN 代码块。它也运行得早!
}
print "Main script execution continues.\n"; # 主脚本执行继续。
1;

执行上述代码后,输出如下:

This is inside a BEGIN block. It runs first!
This is another BEGIN block. It also runs early!
Main script execution starts here.
Main script execution continues.
This is inside an END block. It runs last!

Perl 模块是在库文件中定义的、可重用的包。按照约定,文件名与包名相同,扩展名为 .pm(例如,包 Foo 在 Foo.pm 中)。模块是组织和分发可重用 Perl 代码的主要方式。

一个简单的模块文件 MyUtils.pm 可能如下所示:

MyUtils.pm
package MyUtils;
use strict;
use warnings;
# 这个模块将默认导出 'greet'
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(greet);
# 这个函数可以被导出
sub greet {
my ($name) = @_;
print "Hello, $name!\n";
}
# 这个函数默认不导出
sub internal_helper {
print "This is an internal helper.\n";
}
1; # 重要:模块必须以真值结束。

关于 Perl 模块的关键点:

  • 模块促进代码组织和可重用性。
  • use 和 require 语句用于加载模块。
  • use 和 require 都会在 @INC 数组中列出的目录中搜索模块文件。
  • use ModuleName; 等价于 BEGIN { require ModuleName; ModuleName->import(); }。它在编译时加载模块并调用其 import 方法(通常继承自 Exporter,以便将符号导入调用者的命名空间)。
  • require ModuleName; 在运行时加载模块,不自动调用 import。
  • 模块文件末尾的 1; 确保其编译(并通过 require 或 use 加载)求值为真值,表示成功。

你可以使用 require 加载模块。当你这样做时,通常需要完全限定你从模块调用的子例程的名称:

#!/usr/bin/env perl
use strict;
use warnings;
# 假设 MyUtils.pm 在 @INC 中的某个目录中
require MyUtils;
MyUtils::greet("World"); # 必须使用完整的包名
# MyUtils::internal_helper(); # 如果显式调用,这也将起作用

use 更常用于加载模块。它在编译时加载模块,并且默认情况下调用模块的 import 方法。如果模块使用 Exporter,这可以将特定的子例程(和变量)带入你当前的命名空间,允许你在调用它们时无需使用包限定符。

#!/usr/bin/env perl
use strict;
use warnings;
# 假设 MyUtils.pm 在 @INC 中的某个目录中
use MyUtils; # 加载 MyUtils.pm 并调用 MyUtils->import()
greet("Again"); # 'greet' 已被导出,因此无需 'MyUtils::'
# internal_helper(); # 这将是错误,因为它没有被导出。

为了使符号可用于导入,模块通常使用 Exporter 模块:

# MyUtils.pm 内部
package MyUtils;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter); # 继承自 Exporter
our @EXPORT = qw(greet); # 默认导出 'greet'
our @EXPORT_OK = qw(another_func); # 按请求导出 'another_func'
our %EXPORT_TAGS = (
all => [ qw(greet another_func) ],
extra => [ qw(another_func) ]
);
sub greet { my ($name) = @_; print "Hello, $name from MyUtils!\n"; }
sub another_func { print "This is another_func from MyUtils!\n"; }
1;

然后,脚本可以像这样使用它:

#!/usr/bin/env perl
use strict;
use warnings;
use MyUtils; # 导入 'greet'(来自 @EXPORT)
greet("Default User"); # 默认用户
# 也要导入 'another_func':
# use MyUtils qw(greet another_func);
# 或者
# use MyUtils qw(:all); # 导入 @EXPORT 和 %EXPORT_TAGS{all} 中的所有内容
# another_func();

创建现代 Perl 模块分发版 (Creating a Modern Perl Module Distribution)

Section titled “创建现代 Perl 模块分发版 (Creating a Modern Perl Module Distribution)”

虽然 h2xs 在历史上曾被使用,但现代 Perl 开发依赖于更高级的工具来创建、构建、测试和分发模块。这些工具帮助管理依赖项、自动化测试并遵循 CPAN(Comprehensive Perl Archive Network)最佳实践。

  • 创作工具 (Authoring Tools): Dist::Zilla (dzil)、Minilla (minil) 或 App::ModuleBuild::Tiny 等工具简化了分发骨架、Makefile.PL 或 Build.PL 文件、测试设置和元数据文件 (META.json/META.yml) 的创建。
    • 使用 Minilla 的示例:$ minil new My::Module 创建一个新的分发结构。
    • 使用 Dist::Zilla 的示例:$ dzil new My::Module 创建由 Dist::Zilla 插件管理的分发版。
  • 这些工具通常生成标准目录结构:
  • MANIFEST 等文件(分发版中所有文件的列表)通常是自动生成的。

CPAN 是 Perl 模块的中央仓库。从 CPAN 安装模块最简单的方式是使用 CPAN 客户端。

  • cpanm (App::cpanminus): 这是一个流行、快速且感知依赖的 CPAN 客户端。它通常是安装模块的推荐方式。
    • 安装 cpanm 本身(通常只需一次):$ curl -L https://cpanmin.us | perl - App::cpanminus(或通过系统包管理器)。
    • 安装模块:$ cpanm Module::Name
    • 示例:$ cpanm DateTime
  • 传统 CPAN Shell: Perl 自带一个 cpan 工具。
    • 启动它:$ cpan
    • 安装模块:cpan> install Module::Name
  • 如果你有已下载的模块分发版(例如,My-Module-0.01.tar.gz):
    1. 解压:$ tar -xzf My-Module-0.01.tar.gz
    1. 更改目录:$ cd My-Module-0.01/
    1. 如果它有 Makefile.PL(使用 ExtUtils::MakeMaker):
  • `$ perl Makefile.PL`
  • `$ make`
  • `$ make test`(可选,但推荐)
  • `$ make install`(可能需要 root/管理员权限)
    1. 如果它有 Build.PL(使用 Module::Build 或类似工具):
  • `$ perl Build.PL`
  • `$ ./Build`
  • `$ ./Build test`(可选,但推荐)
  • `$ ./Build install`(可能需要 root/管理员权限)

Perl 在存储在全局数组 @INC 中的目录列表里搜索模块。你可以通过运行 perl -V 或 perl -e 'print "$_\n" for @INC;' 来查看这些目录。