是否可以在其他名称空间中导入(use
)perl模块?
假设我有一个模块A
(没有导出方法@EXPORT
的XS模块为空),我无法更改该模块。
此模块具有方法A::open
目前,我可以通过调用A::open
在我的主程序(主程序包)中使用该模块,我希望将该模块包含在package main
中,以便可以直接调用open
< / p>
我尝试将%A::
的每个键手动推入%main::
,但是没有按预期工作。
我知道实现自己想要的唯一方法是在主程序中使用package A;
,将程序的程序包从main
更改为A
。
我对此不满意。我真的很想将我的程序保留在包main中。
有什么方法可以实现并且仍将程序保留在main包中吗?
Offtopic:是的,我知道通常您不希望将所有内容都导入到您的命名空间中,但是我们广泛使用了此模块,并且我们不想键入A::(实际上,模块名更长了一点)这并不能使情况变得更好)在成千上万的电话面前
答案 0 :(得分:3)
这是那些“不可能”的情况之一,其中明确的解决方案(重做该模块)已超出限制。
但是,您可以 将该软件包的子名称(从符号表中)命名为main
中的相同名称。比粗鲁更糟糕的是,它带有一个小故障:它捕获以任何方式本身打包导入的所有名称。但是,由于此程序包是固定数量的,因此有理由您可以建立该列表(甚至对其进行硬编码)。就这一次吧?
主要
use warnings;
use strict;
use feature 'say';
use OffLimits;
GET_SUBS: {
# The list of names to be excluded
my $re_exclude = qr/^(?:BEGIN|import)$/; # ...
my @subs = grep { !/$re_exclude/ } sort keys %OffLimits::;
no strict 'refs';
for my $sub_name (@subs) {
*{ $sub_name } = \&{ 'OffLimits::' . $sub_name };
}
};
my $name = name('name() called from ' . __PACKAGE__);
my $id = id('id() called from ' . __PACKAGE__);
say "name() returned: $name";
say "id() returned: $id";
与OffLimits.pm
package OffLimits;
use warnings;
use strict;
sub name { return "In " . __PACKAGE__ . ": @_" }
sub id { return "In " . __PACKAGE__ . ": @_" }
1;
它打印
name() returned: In OffLimits: name() called from main id() returned: In OffLimits: id() called from main
根据其他详细信息,您可能需要在BEGIN
块中使用该代码。
当然,另一个选择是硬编码要“导出”的潜艇(在@subs
中)。鉴于模块实际上是不可变的,因此该选项是合理且更可靠的。
这也可以包装在模块中,这样您就可以正常,选择性地进行导入。
WrapOffLimits.pm
package WrapOffLimits;
use warnings;
use strict;
use OffLimits;
use Exporter qw(import);
our @sub_names;
our @EXPORT_OK = @sub_names;
our %EXPORT_TAGS = (all => \@sub_names);
BEGIN {
# Or supply a hard-coded list of all module's subs in @sub_names
my $re_exclude = qr/^(?:BEGIN|import)$/; # ...
@sub_names = grep { !/$re_exclude/ } sort keys %OffLimits::;
no strict 'refs';
for my $sub_name (@sub_names) {
*{ $sub_name } = \&{ 'OffLimits::' . $sub_name };
}
};
1;
现在在调用方中,您只能导入一些子
use WrapOffLimits qw(name);
或全部
use WrapOffLimits qw(:all);
具有与上述测试相同的主要功能。
模块名称是硬编码的,应该可以,因为这仅适用于该模块。
添加以下内容主要是为了完整性。
可以通过编写自己的import
子程序将模块名称传递给包装器,然后使用该子程序。也可以传递导入列表,但要花费use
语句的尴尬界面。
它遵循
package WrapModule;
use warnings;
use strict;
use OffLimits;
use Exporter qw(); # will need our own import
our ($mod_name, @sub_names);
our @EXPORT_OK = @sub_names;
our %EXPORT_TAGS = (all => \@sub_names);
sub import {
my $mod_name = splice @_, 1, 1; # remove mod name from @_ for goto
my $re_exclude = qr/^(?:BEGIN|import)$/; # etc
no strict 'refs';
@sub_names = grep { !/$re_exclude/ } sort keys %{ $mod_name . '::'};
for my $sub_name (@sub_names) {
*{ $sub_name } = \&{ $mod_name . '::' . $sub_name };
}
push @EXPORT_OK, @sub_names;
goto &Exporter::import;
}
1;
可用作什么
use WrapModule qw(OffLimits name id); # or (OffLimits :all)
或者,将列表拆分,以便提醒用户不寻常的界面
use WrapModule 'OffLimits', qw(name id);
与上面的主电源一起使用时,将输出相同的输出。
use
语句最终使用模块中定义的import子项,该子项通过写入调用者的符号表来导出符号。 (如果未编写任何import
子项,则很好地使用Exporter
的{{1}}方法,这通常是这样做的。)
这样,我们可以解压缩参数并在import
调用时提供模块名称。现在还需要提供导入列表,我们必须手动将use
移至push
,因为这不能进入@EXPORT_OK
阶段。最后,通过goto的(良好形式)用BEGIN
替换了子项,以完成工作。
答案 1 :(得分:1)
您可以使用glob分配强制将函数“导入”到主函数中,以为子例程添加别名(并且您想在BEGIN中进行此操作,以便它在编译时发生,然后在文件中稍后对该子例程的调用进行解析):< / p>
7
但是,您可能在这里遇到的另一个问题是open是一个内置函数,可能会导致某些problems。您可以添加use strict;
use warnings;
use Other::Module;
BEGIN { *open = \&Other::Module::open }
来表示在这种情况下要覆盖内置函数,因为您没有使用实际的导入函数来这样做。
答案 2 :(得分:1)
这就是我现在想出的。是的,这很hacky,是的,我也觉得我用它打开了潘多拉魔盒。但是,至少有一个小的虚拟程序运行得很好。
我再次在代码中重命名了模块。在我的原始帖子中,我使用了示例A::open
,实际上,此模块不包含perl内核保留的任何方法/变量。这就是为什么我在这里盲目导入所有内容的原因。
BEGIN {
# using the caller to determine the parent. Usually this is main but maybe we want it somewhere else in some cases
my ($parent_package) = caller;
package A;
foreach (keys(%A::)) {
if (defined $$_) {
eval '*'.$parent_package.'::'.$_.' = \$A::'.$_;
}
elsif (%$_) {
eval '*'.$parent_package.'::'.$_.' = \%A::'.$_;
}
elsif (@$_) {
eval '*'.$parent_package.'::'.$_.' = \@A::'.$_;
}
else {
eval '*'.$parent_package.'::'.$_.' = \&A::'.$_;
}
}
}