内容

名称

Exporter - 为模块实现默认导入方法

概要

在模块 YourModule.pm

package YourModule;
use Exporter 'import';
our @EXPORT_OK = qw(munge frobnicate);  # symbols to export on request

package YourModule;
require Exporter;
our @ISA = qw(Exporter);  # inherit all of Exporter's methods
our @EXPORT_OK = qw(munge frobnicate);  # symbols to export on request

package YourModule;
use parent 'Exporter';  # inherit all of Exporter's methods
our @EXPORT_OK = qw(munge frobnicate);  # symbols to export on request

在希望使用 YourModule 的其他文件中

use YourModule qw(frobnicate);      # import listed symbols
frobnicate ($left, $right)          # calls YourModule::frobnicate

查看 "良好实践" 了解您可能希望在现代 Perl 代码中使用的一些变体。

描述

Exporter 模块实现了一个 import 方法,允许模块将函数和变量导出到用户的命名空间。许多模块使用 Exporter 而不是实现自己的 import 方法,因为 Exporter 提供了一个高度灵活的接口,其实现针对常见情况进行了优化。

当处理模块的 use 语句时,Perl 会自动调用 import 方法。模块和 useperlfuncperlmod 中有说明。了解模块的概念以及 use 语句的工作原理对于理解 Exporter 至关重要。

如何导出

模块中的数组 @EXPORT@EXPORT_OK 分别保存着将默认导出到用户命名空间的符号列表,或者用户可以请求导出的符号列表。这些符号可以表示函数、标量、数组、哈希或类型全局变量。这些符号必须使用全名给出,但函数前面的符号 '&' 可选,例如:

our @EXPORT    = qw(afunc $scalar @array);   # afunc is a function
our @EXPORT_OK = qw(&bfunc %hash *typeglob); # explicit prefix on &bfunc

如果您只导出函数名,建议省略符号 '&',因为这种实现方式更快。

选择要导出的内容

不要导出方法名!

不要默认导出其他任何内容,除非有充分的理由!

导出会污染模块用户的命名空间。如果您必须导出,请尝试使用 @EXPORT_OK 而不是 @EXPORT,并避免使用简短或常见的符号名称,以减少命名冲突的风险。

通常,任何未导出的内容仍然可以通过 YourModule::item_name(或 $blessed_ref->method)语法从模块外部访问。按照惯例,您可以在名称前面使用下划线来非正式地表明它们是“内部的”,不供公共使用。

(实际上,可以通过以下方式获取私有函数:

my $subref = sub { ... };
$subref->(@args);            # Call it as a function
$obj->$subref(@args);        # Use it as a method

但是,如果您将它们用于方法,则需要自行解决如何实现继承。)

一般来说,如果模块试图面向对象,则不要导出任何内容。如果它只是一个函数集合,则将任何内容 @EXPORT_OK,但谨慎使用 @EXPORT。对于函数和方法名称,请优先使用裸词,而不是在导出列表中使用以符号 '&' 为前缀的名称。

可以在 perlmod 中找到其他模块设计指南。

如何导入

在其他希望使用您的模块的文件中,它们有三种基本方法来加载您的模块并导入其符号

use YourModule;

这会将 YourModule 的 @EXPORT 中的所有符号导入到 use 语句的命名空间中。

use YourModule ();

这会导致 perl 加载你的模块,但不会导入任何符号。

use YourModule qw(...);

这只会将调用者列出的符号导入到它们的命名空间中。所有列出的符号必须在你的 @EXPORT@EXPORT_OK 中,否则会发生错误。Exporter 的高级导出功能可以通过这种方式访问,但列表条目在语法上与符号名称不同。

除非你想要使用它的高级功能,否则这可能就是你需要知道的关于使用 Exporter 的所有内容。

高级功能

专门的导入列表

如果导入列表中的任何条目以 !、: 或 / 开头,则该列表将被视为一系列规范,这些规范要么添加到要导入的名称列表中,要么从该列表中删除。它们从左到右处理。规范的形式为

[!]name         This name only
[!]:DEFAULT     All names in @EXPORT
[!]:tag         All names in $EXPORT_TAGS{tag} anonymous array
[!]/pattern/    All names in @EXPORT and @EXPORT_OK which match

以 ! 开头的表示应从要导入的名称列表中删除匹配的名称。如果第一个规范是删除,则它被视为在 :DEFAULT 之前。如果你只想导入除默认集之外的额外名称,你仍然需要显式地包含 :DEFAULT。

例如,Module.pm 定义

our @EXPORT      = qw(A1 A2 A3 A4 A5);
our @EXPORT_OK   = qw(B1 B2 B3 B4 B5);
our %EXPORT_TAGS = (T1 => [qw(A1 A2 B1 B2)], T2 => [qw(A1 A2 B3 B4)]);

请注意,你不能在 @EXPORT 或 @EXPORT_OK 中使用标签。

EXPORT_TAGS 中的名称也必须出现在 @EXPORT 或 @EXPORT_OK 中。

使用 Module 的应用程序可以这样说

use Module qw(:DEFAULT :T2 !B3 A3);

其他示例包括

use Socket qw(!/^[AP]F_/ !SOMAXCONN !SOL_SOCKET);
use POSIX  qw(:errno_h :termios_h !TCSADRAIN !/^EXIT/);

请记住,大多数模式(使用 //)需要在开头使用 ^ 锚定,例如 /^EXIT/ 而不是 /EXIT/

你可以说 BEGIN { $Exporter::Verbose=1 } 来查看规范是如何被处理的,以及实际上哪些内容被导入到模块中。

不使用 Exporter 的 import 方法进行导出

Exporter 有一个特殊的方法,'export_to_level',它用于你无法直接调用 Exporter 的 import 方法的情况。export_to_level 方法看起来像

    MyPackage->export_to_level(
	$where_to_export, $package, @what_to_export
    );

其中 $where_to_export 是一个整数,表示要将你的符号导出到调用堆栈的向上多少层,@what_to_export 是一个数组,表示要导出哪些符号(通常是 @_)。$package 参数目前未使用。

例如,假设您有一个模块 A,它已经有一个导入函数

    package A;

    our @ISA = qw(Exporter);
    our @EXPORT_OK = qw($b);

    sub import
    {
	$A::b = 1;     # not a very useful import method
    }

并且您想将符号 $A::b 导出回调用包 A 的模块。由于 Exporter 依赖于导入方法通过继承工作,因此 Exporter::import() 永远不会被调用。相反,请说以下内容

    package A;
    our @ISA = qw(Exporter);
    our @EXPORT_OK = qw($b);

    sub import
    {
	$A::b = 1;
	A->export_to_level(1, @_);
    }

这将导出当前包“之上”一级的符号 - 即:导出到使用包 A 的程序或模块。

注意:在调用 export_to_level 之前,请务必不要修改 @_ - 否则使用您包的人将得到非常无法解释的结果!

不继承 Exporter 的导出

通过在您的 @ISA 中包含 Exporter,您继承了 Exporter 的 import() 方法,但您还继承了一些其他辅助方法,这些方法您可能不想要,并且会使继承树复杂化。为了避免这种情况,您可以执行以下操作

package YourModule;
use Exporter qw(import);

这将把 Exporter 自己的 import() 方法导出到 YourModule。一切将像以前一样工作,但您无需在 @YourModule::ISA 中包含 Exporter。

注意:此功能是在 Exporter 的 5.57 版本中引入的,该版本随 perl 5.8.3 发布。

模块版本检查

Exporter 模块将尝试从模块导入数字转换为调用 $module_name->VERSION($value)。这可用于验证所用模块的版本是否大于或等于所需版本。

出于历史原因,Exporter 提供了一个 require_version 方法,该方法只是委托给 VERSION。最初,在 UNIVERSAL::VERSION 存在之前,Exporter 会调用 require_version

由于 UNIVERSAL::VERSION 方法将 $VERSION 数字视为一个简单的数值,因此它会认为版本 1.10 低于 1.9。因此,强烈建议您使用至少两位小数的数字,例如 1.09。

管理未知符号

在某些情况下,您可能希望阻止导出某些符号。通常,这适用于在某些系统上可能不存在函数或常量的扩展。

任何无法导出的符号的名称应列在 @EXPORT_FAIL 数组中。

如果模块尝试导入任何这些符号,Exporter 将让模块有机会在生成错误之前处理这种情况。Exporter 将调用一个 export_fail 方法,其中包含失败符号的列表

@failed_symbols = $module_name->export_fail(@failed_symbols);

如果 export_fail 方法返回一个空列表,则不会记录任何错误,并且所有请求的符号都会被导出。如果返回的列表不为空,则会为每个符号生成一个错误,并且导出失败。Exporter 提供了一个默认的 export_fail 方法,它只是返回未更改的列表。

export_fail 方法的用途包括为某些符号提供更好的错误消息,以及执行延迟的架构检查(默认情况下将更多符号放入 @EXPORT_FAIL 中,然后如果有人实际尝试使用它们,并且昂贵的检查表明它们在该平台上可用,则将其取出)。

标签处理实用程序函数

由于 %EXPORT_TAGS 中列出的符号也必须出现在 @EXPORT@EXPORT_OK 中,因此提供了两个实用程序函数,允许您轻松地将标记的符号集添加到 @EXPORT@EXPORT_OK 中。

our %EXPORT_TAGS = (foo => [qw(aa bb cc)], bar => [qw(aa cc dd)]);

Exporter::export_tags('foo');     # add aa, bb and cc to @EXPORT
Exporter::export_ok_tags('bar');  # add aa, cc and dd to @EXPORT_OK

任何不是标签的名称都会被添加到 @EXPORT@EXPORT_OK 中,但不会更改,但会触发警告(使用 -w),以避免拼写错误的标签名称被静默添加到 @EXPORT@EXPORT_OK 中。未来的版本可能会将其更改为致命错误。

生成组合标签

如果 %EXPORT_TAGS 中存在多个符号类别,通常创建实用程序 ":all" 来简化 "use" 语句。

最简单的方法是

our  %EXPORT_TAGS = (foo => [qw(aa bb cc)], bar => [qw(aa cc dd)]);

 # add all the other ":class" tags to the ":all" class,
 # deleting duplicates
 {
   my %seen;

   push @{$EXPORT_TAGS{all}},
     grep {!$seen{$_}++} @{$EXPORT_TAGS{$_}} foreach keys %EXPORT_TAGS;
 }

CGI.pm 创建了一个 ":all" 标签,其中包含一些(但不是全部)类别。可以通过一个小的更改来实现这一点

# add some of the other ":class" tags to the ":all" class,
# deleting duplicates
{
  my %seen;

  push @{$EXPORT_TAGS{all}},
    grep {!$seen{$_}++} @{$EXPORT_TAGS{$_}}
      foreach qw/html2 html3 netscape form cgi internal/;
}

请注意,%EXPORT_TAGS 中的标签名称没有前导 ":".

AUTOLOADed 常量

许多模块使用 AUTOLOADing 来处理常量子例程,以避免必须编译并浪费内存来存储很少使用的值(有关常量子例程的详细信息,请参阅 perlsub)。对这些常量子例程的调用在编译时不会被优化掉,因为它们在编译时无法检查其常量性。

即使在编译时原型可用,子例程的主体也不可用(它尚未被 AUTOLOAD)。perl 需要在编译时检查子例程的 () 原型和主体,以检测它是否可以安全地将对该子例程的调用替换为常量值。

解决此问题的一种方法是在BEGIN块中调用一次常量

package My ;

use Socket ;

foo( SO_LINGER );  ## SO_LINGER NOT optimized away; called at runtime
BEGIN { SO_LINGER }
foo( SO_LINGER );  ## SO_LINGER optimized away at compile time.

这将强制SO_LINGERAUTOLOADMy包中稍后遇到SO_LINGER之前发生。

如果您正在编写一个AUTOLOAD的包,请考虑强制对其他包显式导入的任何常量或在使用您的包时通常使用的常量进行AUTOLOAD

最佳实践

声明@EXPORT_OK及其朋友

在使用Exporter以及标准的strictwarnings pragma时,需要使用our关键字来声明包变量@EXPORT_OK@EXPORT@ISA等。

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(munge frobnicate);

如果对低于5.6版本的Perl的向后兼容性很重要,则必须改为编写use vars语句。

use vars qw(@ISA @EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT_OK = qw(munge frobnicate);

安全起见

使用运行时语句(如require Exporter)和对包变量的赋值有一些注意事项,对于不知情的程序员来说,这些注意事项可能非常微妙。例如,这可能会发生在相互递归的模块中,这些模块会受到相关构造执行时间的影響。

永远不必考虑这个问题的理想方法是使用BEGIN块和简单的导入方法。因此,"SYNOPSIS"代码的第一部分可以改写为

package YourModule;

use strict;
use warnings;

use Exporter 'import';
BEGIN {
  our @EXPORT_OK = qw(munge frobnicate);  # symbols to export on request
}

或者,如果您需要从Exporter继承

package YourModule;

use strict;
use warnings;

BEGIN {
  require Exporter;
  our @ISA = qw(Exporter);  # inherit all of Exporter's methods
  our @EXPORT_OK = qw(munge frobnicate);  # symbols to export on request
}

BEGIN将确保立即加载Exporter.pm以及对@ISA@EXPORT_OK的赋值,就像use一样,没有空间让任何事情出错或完全错误。

关于加载Exporter和继承,可以使用baseparent等模块作为替代方案。

use base qw(Exporter);
# or
use parent qw(Exporter);

所有这些语句都是BEGIN { require Exporter; our @ISA = qw(Exporter); }的良好替代方案,具有相同的编译时效果。基本区别在于base代码与声明的fields交互,而parent是旧base代码的简化版本,仅用于建立IS-A关系。

有关更多详细信息,请参阅baseparent的文档和代码。

另一种彻底解决运行时与编译时陷阱的方法是使用Exporter::Easy,它是一个Exporter的包装器,允许在use语句中一次性完成所有样板代码。

use Exporter::Easy (
    OK => [ qw(munge frobnicate) ],
);
# @ISA setup is automatic
# all assignments happen at compile time

不要导出的内容

"选择要导出的内容" 中已经警告过您,不要导出

还有一个需要添加到此列表中的内容。不要导出变量名。即使 Exporter 允许您这样做,也不意味着您应该这样做。

@EXPORT_OK = qw($svar @avar %hvar); # DON'T!

导出变量不是一个好主意。它们可能会在幕后发生变化,从而引发难以追踪和修复的远程可怕影响。相信我:它们不值得。

为了提供设置/获取类级设置的功能,最好提供访问器作为子例程或类方法。

另请参阅

Exporter 绝对不是唯一具有符号导出功能的模块。在 CPAN 上,您可能会找到很多这样的模块。有些更轻量级。有些提供改进的 API 和功能。选择最适合您需求的模块。以下是一些此类模块的示例列表。

Exporter::Easy
Exporter::Lite
Exporter::Renaming
Exporter::Tidy
Sub::Exporter / Sub::Installer
Perl6::Export / Perl6::Export::Attrs

许可证

此库是自由软件。您可以根据与 Perl 本身相同的条款重新分发和/或修改它。