根据 Perl Critic 我们应该“通过
@EXPORT_OK
或 %EXPORT_TAGS
而不是 @EXPORT
导出符号”
按照此建议,我将模块从
@EXPORT_OK
转换为 @EXPORT
,但我在使用 Exporter
周围的功能时遇到困难。下面我将举一个例子来说明这种情况。
最初我的代码看起来像这样,并且按预期工作:
lib/MyAnimals/Cats.pm
package MyAnimals::Cats;
use parent qw(Exporter);
our @EXPORT = qw(tabby calico abyssinian tiger panther leopard);
lib/MyAnimals/Dogs.pm
package MyAnimals::Dogs;
use parent qw(Exporter);
our @EXPORT = qw(dachshund poodle great_dane st_bernard);
lib/MyAnimals.pm
package MyAnimals;
$Exporter::Verbose = 0;
sub import {
my $self = shift;
my $caller = caller;
foreach my $package (@_) {
my $full_package = "MyAnimals::$package";
eval "require $full_package";
if ($@) {
warn "Could not require MyAnimals::$package: $@";
}
$full_package->Exporter::export($caller);
}
return;
}
=head2 How it works
The MyAnimals module simply imports functions from MyAnimals::* modules.
Each module defines a self-contained functions, and puts those function
names into @EXPORT. MyAnimals defines its own import function, but that
does not matter to the plug-in modules.
This function is taken from brian d foy's Test::Data module. Thanks brian!
这让我可以在我的程序中:
program.pl
#!/usr/bin/env perl
use MyAnimals qw(Cats Dogs);
将所有的猫和狗(函数)导入到程序中。
但是,由于通过导出所有内容而不是仅导出用户要求的内容来污染用户的命名空间被认为是不好的形式,因此我将转向以下代码:
lib/MyAnimals/Cats.pm
package MyAnimals::Cats;
use parent qw(Exporter);
our %EXPORT_TAGS = (house => [qw(tabby calico abyssinian)], jungle => [qw(tiger panther leopard)]);
Exporter::export_ok_tags('house'); # add tabby calico abyssinian to @EXPORT_OK
Exporter::export_ok_tags('jungle'); # add tiger panther leopard to @EXPORT_OK
# 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;
}
lib/MyAnimals/Dogs.pm
package MyAnimals::Dogs;
use parent qw(Exporter);
our @EXPORT = qw(dachshund poodle great_dane st_bernard);
our %EXPORT_TAGS = (small => [qw(dachshund poodle)], big => [qw(great_dane st_bernard)]);
Exporter::export_ok_tags('small'); # add dachshund poodle to @EXPORT_OK
Exporter::export_ok_tags('big'); # add great_dane st_bernard to @EXPORT_OK
# 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;
}
现在允许我的程序仅导入它想要的动物:
program.pl
#!/usr/bin/env perl
use MyAnimals::Cats qw(:jungle tabby);
use MyAnimals::Dogs qw(:big);
但是,我已经失去了
MyAnimals.pm
的功能,特别是在我真正想要的情况下,我不再有一种简单、干净的方式来导入所有内容,例如在测试中:
t/01-no_longer_works.t
use MyAnimals qw(Cats Dogs);
现在我必须这样做: t/02-works_but_is_not_elegant.t
use MyAnimals::Cats qw(:all);
use MyAnimals::Dogs qw(:all);
我正在寻找的是一种改变
MyAnimals.pm
的方法,以便测试t/01-no_longer_works.t
像以前一样运行(当我使用@EXPORT时)并导入所有动物(函数)。
首先,不要从 Exporter 继承(除非您自己创建一个更高级、更具体的导出器)。得到它的
import
例程就够了:
use Exporter qw(import);
接下来,看起来您的大部分问题是您想要从
MyAnimals::Cats
到 MyAnimals
导出到最终调用者。这是我通常会尽力避免的事情。玩弄这类事情很好,但我不想在生产代码中处理它。
话虽如此,
Exporter
还有另一个例程,export_to_level
,它允许您进一步向链上游导出。有了这个,您可以有效地跳过该 MyAnimals
并导出到任何名为 MyAnimals
的内容。
这里是
MyAnimals
,其中你必须定义自己的import
并做很多工作。请记住,您不能只要求每个模块导入所有内容,因为它们只导出自己的内容(尽管我猜您可以通过它们具有相同的导出标签来闲逛)。我已经放弃了所有的簿记和摆弄:
package MyAnimals;
sub import {
shift;
require MyAnimals::Cats;
MyAnimals::Cats->import( @_ );
}
1;
现在,
MyAnimals::Cats
,现在是一个专门的导入器,因此它继承自Exporter
。否则你无法到达export_to_level
:
package MyAnimals::Cats;
use parent qw(Exporter);
您可能还想查看
Modern::Perl
的源代码,看看它如何从不同来源导入一堆东西。
sub import {
__PACKAGE__->export_to_level(2, @_);
}
our @EXPORT_OK = qw(foo bar);
sub foo { print "foo from Cats\n"; }
sub bar { print "bar from Cats\n"; }
1;
然后是测试程序,它只加载
MyAnimals
并要求两件事:
use lib qw(.);
use MyAnimals qw(foo bar);
foo();
bar();
输出显示我得到了我想要的:
$ perl test.pl
foo from Cats
bar from Cats
但是,如果有任何其他方法可以让它发挥作用,这不是一个好的设计。有多种方法可以识别您想要加载的模块,然后使用
:all
标签调用它们的导入例程,如您所示。然而,您还是回到了开始导出所有内容的地方。如果你打算这样做,那就更复杂了,什么都不做就可以省去麻烦。
请记住,没有上下文就没有最佳实践。 Perl 最佳实践 从来都不是硬性规定(再次阅读 Damian 的介绍)。做对当前问题有意义的事情。在很多情况下,我宁愿使用可以关闭的默认导出,而不是其他东西
use File::Basename; # I probably want the usual suspects (common)
use File::Basename qw(); # I want nothing (more rare)
use File::Basename qw(dirname); # I want one thing only; this is the code either way