要求 Perl 指定的 VERSION 版本,或要求 EXPR 或 $_
(如果未提供 EXPR)指定的一些语义。
VERSION 可以是 v5.24.1 等文字,它将与 $^V
(或 英语中的 $PERL_VERSION
)进行比较,也可以是 5.024001 形式的数字参数,它将与 $]
进行比较。如果 VERSION 大于当前 Perl 解释器的版本,则会引发异常。与 use
进行比较,它可以在编译时进行类似的检查。
通常应避免将 VERSION 指定为 5.024001 形式的数字参数,因为与 v5.24.1 相比,它是一种较旧、较难读的语法。在 perl 5.8.0(2002 年发布)之前,更详细的数字形式是唯一受支持的语法,这就是为什么您可能会在较旧的代码中看到它。
require v5.24.1; # run time version check
require 5.24.1; # ditto
require 5.024_001; # ditto; older syntax compatible
with perl 5.6
否则,require
要求包含库文件(如果尚未包含)。该文件通过 do-FILE 机制包含,该机制本质上只是 eval
的一种变体,但调用脚本中的词法变量对于包含的代码是不可见的。如果它在纯 Perl 中实现,则它的语义类似于以下内容
use Carp 'croak';
use version;
sub require {
my ($filename) = @_;
if ( my $version = eval { version->parse($filename) } ) {
if ( $version > $^V ) {
my $vn = $version->normal;
croak "Perl $vn required--this is only $^V, stopped";
}
return 1;
}
if (exists $INC{$filename}) {
return 1 if $INC{$filename};
croak "Compilation failed in require";
}
local $INC;
# this type of loop lets a hook overwrite $INC if they wish
for($INC = 0; $INC < @INC; $INC++) {
my $prefix = $INC[$INC];
if (!defined $prefix) {
next;
}
if (ref $prefix) {
#... do other stuff - see text below ....
}
# (see text below about possible appending of .pmc
# suffix to $filename)
my $realfilename = "$prefix/$filename";
next if ! -e $realfilename || -d _ || -b _;
$INC{$filename} = $realfilename;
my $result = do($realfilename);
# but run in caller's namespace
if (!defined $result) {
$INC{$filename} = undef;
croak $@ ? "$@Compilation failed in require"
: "Can't locate $filename: $!\n";
}
if (!$result) {
delete $INC{$filename};
croak "$filename did not return true value";
}
$! = 0;
return $result;
}
croak "Can't locate $filename in \@INC ...";
}
请注意,该文件不会在同一指定名称下包含两次。
历史上,文件必须将 true 作为最后一条语句返回,以指示任何初始化代码成功执行,因此,除非您确定它会以其他方式返回 true,否则通常以 1;
结束此类文件。但最好只放 1;
,以防您添加更多语句。从 5.37.6 开始,可以通过启用“module_true”功能来避免此要求,该功能在现代版本包中默认启用。因此,带有 use v5.37;
的代码不再需要关注此问题。有关更多详细信息,请参阅 功能。请注意,这会影响使用该功能的编译单元,并且在需要模块之前使用它不会更改不使用该功能的现有模块的行为。
如果 EXPR 是一个裸字,require
假定一个 .pm 扩展名,并用 /
替换文件名中的 ::
,以便于加载标准模块。这种加载模块的形式不会冒着更改您的命名空间的风险,但是它会自动激活所需模块的隐藏变量。
换句话说,如果您尝试这样做
require Foo::Bar; # a splendid bareword
require 函数实际上会在 @INC
数组中指定的目录中查找 Foo/Bar.pm 文件,并且它将在编译时自动激活 Foo::Bar::
隐藏变量。
但如果您尝试这样做
my $class = 'Foo::Bar';
require $class; # $class is not a bareword
#or
require "Foo::Bar"; # not a bareword because of the ""
require 函数将在 @INC
数组中查找 Foo::Bar 文件,并且会抱怨在那里找不到 Foo::Bar。在这种情况下,您可以执行
eval "require $class";
或者您可以执行
require "Foo/Bar.pm";
这两种形式都不会在编译时自动激活任何隐藏变量,并且只具有运行时效果。
现在您已经了解了 require
如何使用裸字参数查找文件,在幕后还有一些额外功能。在 require
查找 .pm 扩展名之前,它将首先查找具有 .pmc 扩展名的类似文件名。如果找到此文件,它将加载在以 .pm 扩展名结尾的任何文件的位置。这适用于显式的 require "Foo/Bar.pm";
形式和 require Foo::Bar;
形式。
您还可以通过将 Perl 代码引用或对象直接放入 @INC
数组中,将挂钩插入导入工具中。有两种类型的挂钩,INC 过滤器和 INCDIR 挂钩,并且有三种形式表示挂钩:子例程引用、数组引用和受祝福的对象。
子例程引用是最简单的情况。当包含系统遍历 @INC
并遇到一个子例程时,除非此子例程已祝福并支持 INCDIR 挂钩,否则此子例程将被假定为 INC 挂钩,将使用两个参数调用它,第一个参数是对其自身的引用,第二个参数是要包含的文件的名称(例如,Foo/Bar.pm)。子例程应返回以下顺序中最多四个值,或不返回任何内容
对标量的引用,其中包含要添加到文件或生成器输出前面的任何初始源代码。
文件句柄,文件将从中读取。
对子例程的引用。如果没有文件句柄(上一个项目),则此子例程应预期在每次调用时生成一行源代码,将该行写入 $_
并返回 1,最后在文件末尾返回 0。如果存在文件句柄,则将调用子例程作为简单的源代码过滤器,其中行在 $_
中读取。同样,对于每行有效行返回 1,在返回所有行后返回 0。出于历史原因,子例程将收到一个无意义的参数(实际上始终为数字值零)作为 $_[0]
。
子例程的可选状态。状态作为 $_[1]
传入。
AUTOLOAD
不能用于解析 INCDIR
方法,首先检查 INC
,而 AUTOLOAD
将解析它。
如果返回空列表、undef
或与上述前 3 个值不匹配的任何内容,则 require
将查看 @INC
的剩余元素。请注意,此文件句柄必须是真实的文件句柄(严格来说是类型全局或对类型全局的引用,无论是否祝福);绑定的文件句柄将被忽略,并且处理将在此处停止。
如果挂钩是一个对象,它应提供一个 INC
或 INCDIR
方法,该方法将按上述方式调用,第一个参数是对象本身。如果它不提供任何方法,并且对象不是 CODE 引用,则将抛出异常,否则它将像未祝福的 CODE 引用一样执行。请注意,在声明 INC
子例程时,必须完全限定方法名称(与 INCDIR
子例程不同),因为不合格的符号 INC
总是强制进入包 main
。以下是 INC
挂钩的典型代码布局
# In Foo.pm
package Foo;
sub new { ... }
sub Foo::INC {
my ($self, $filename) = @_;
...
}
# In the main program
push @INC, Foo->new(...);
如果挂钩是数组引用,则其第一个元素必须是子例程引用或如上所述的对象。当第一个元素是支持 INC
或 INCDIR
方法的对象时,将使用该对象作为第一个参数、请求的文件名作为第二个参数以及挂钩数组引用作为第三个参数来调用该方法。当第一个元素是子例程时,将使用数组作为第一个参数、文件名作为第二个参数来调用它,不会传入第三个参数。在这两种形式中,你都可以修改数组的内容,以便在调用之间提供状态或你喜欢的任何内容。
换句话说,你可以写
push @INC, \&my_sub;
sub my_sub {
my ($coderef, $filename) = @_; # $coderef is \&my_sub
...
}
或
push @INC, [ \&my_sub, $x, $y, ... ];
sub my_sub {
my ($arrayref, $filename) = @_;
# Retrieve $x, $y, ...
my (undef, @parameters) = @$arrayref;
...
}
或
push @INC, [ HookObj->new(), $x, $y, ... ];
sub HookObj::INC {
my ($self, $filename, $arrayref)= @_;
my (undef, @parameters) = @$arrayref;
...
}
这些钩子还允许设置它们已加载的文件对应的 %INC
条目。请参阅 perlvar 中的“%INC”。如果 INC
钩子没有这样做,则 perl 会将 %INC
条目设置为钩子引用本身。
钩子还可以用于重写 @INC
数组。虽然这听起来很奇怪,但在某些情况下这样做非常有用。此类钩子通常只返回 undef,并且不混合过滤和 @INC
修改。虽然在旧版本的 perl 中,让钩子修改 @INC
充满了问题,甚至可能导致段错误或断言失败,但从 5.37.7 开始,逻辑变得更加健壮,并且钩子现在可以控制循环迭代(如果它希望这样做的话)。
现在有一个设施来控制在 require 期间执行的 @INC
数组遍历的迭代器。$INC
变量将使用当前执行的钩子的索引进行初始化。一旦钩子返回,将在 @INC
中检查的下一个槽将是 $INC
中值的整数后继(如果它为 undef,则为 -1)。例如,以下代码
push @INC, sub {
splice @INC, $INC, 1; # remove this hook from @INC
unshift @INC, sub { warn "A" };
undef $INC; # reset the $INC iterator so we
# execute the newly installed sub
# immediately.
};
将把一个子例程安装到 @INC
中,当作为钩子执行时(例如,通过对不存在的文件进行 require),钩子会将自身从 @INC
中分离出来,并在前面添加一个新的子例程,该子例程将在有人执行需要 @INC
搜索的 require 操作时发出警告,然后立即执行该钩子。
在 5.37.7 之前,没有办法让 perl 立即使用新安装的钩子,或者检查迭代器左侧 @INC
中的任何已更改的项,因此只有在第二次调用 require 时才会生成警告。在较新的 perl 中,未定义 $INC
的最后一条语句的存在将导致 perl 在开头重新启动 @INC
数组的遍历并立即执行新安装的子例程。
无论 $INC
持有什么值(如果有),都将在 require 结束时恢复。在钩子的生命周期内对 $INC
所做的任何更改都将在钩子退出后展开,并且它的值仅在执行钩子后才有意义,因此在执行 require
之前将 $INC
设置为某个值将不会对 require 的执行方式产生任何影响。
从 5.37.7 开始,将静默忽略 undef 的 @INC
值。
函数 require()
很难正确包装。许多模块都会查阅堆栈以查找有关其调用者的信息,而通过包装 require()
来注入一个新的堆栈帧通常会破坏某些内容。尽管如此,在 require
之前和之后执行操作的能力非常有用,例如对于像 Devel::TraceUse
这样的跟踪实用程序,或者测量加载时间和 require 图的内存消耗。由于在 5.37.10 中安全创建 require()
包装器存在困难,因此我们引入了一种新机制。
从 5.37.10 开始,在执行任何其他操作之前,require
将检查 ${^HOOK}{require__before}
是否包含代码引用,如果包含,它将使用正在加载项的文件名形式调用该代码引用。该挂钩可以修改 $_[0]
以加载不同的文件名,或者它可以抛出致命异常以导致 require 失败,这将被视为所需代码本身抛出异常。
${^HOOK}{require__before}
挂钩可以返回代码引用,在这种情况下,该代码引用将在 require 完成后执行(在将文件名作为参数的 eval 中)。无论编译如何完成,甚至 require 抛出致命异常,它都将被执行。该函数可以咨询 %INC
以确定 require 是否失败。例如,以下代码将在每个 require
语句之前和之后打印一些诊断信息。该示例还包括链接信号的逻辑,以便多个信号可以协同工作。行为良好的 ${^HOOK}{require__before}
处理程序应始终考虑这一点。
{
use Scalar::Util qw(reftype);
my $old_hook = ${^HOOK}{require__before};
local ${^HOOK}{require__before} = sub {
my ($name) = @_;
my $old_hook_ret;
$old_hook_ret = $old_hook->($name) if $old_hook;
warn "Requiring: $name\n";
return sub {
$old_hook_ret->() if ref($old_hook_ret)
&& reftype($old_hook_ret) eq "CODE";
warn sprintf "Finished requiring %s: %s\n",
$name, $INC{$name} ? "loaded" :"failed";
};
};
require Whatever;
}
此挂钩对所有 require
语句执行,这与仅对相对文件名执行的 INC
和 INCDIR
挂钩不同,并且它在 require 中的任何其他特殊行为之前首先执行。请注意,${^HOOK}{require__before}
中的初始挂钩*不*在 eval 中执行,并且抛出异常将停止进一步处理,但它可能返回的 after 挂钩在 eval 中执行,并且它抛出的任何异常都将被静默忽略。这是因为它在 require 完成后触发的作用域清理逻辑中执行,并且此时异常不会阻止模块加载等。
有一个类似的挂钩在 require 完成后触发,${^HOOK}{require__after}
,它将在每个 require 语句完成时调用,无论是通过异常还是成功。它将使用最近执行的 require 语句的文件名调用。它在 eval 中执行,并且不会以任何方式影响执行。