不同名称空间中的Perl导入软件包

时间:2018-09-28 20:35:03

标签: perl import namespaces

是否可以在其他名称空间中导入(use)perl模块?

假设我有一个模块A(没有导出方法@EXPORT的XS模块为空),我无法更改该模块。

此模块具有方法A::open

目前,我可以通过调用A::open在我的主程序(主程序包)中使用该模块,我希望将该模块包含在package main中,以便可以直接调用open < / p>

我尝试将%A::的每个键手动推入%main::,但是没有按预期工作。

我知道实现自己想要的唯一方法是在主程序中使用package A;,将程序的程序包从main更改为A。 我对此不满意。我真的很想将我的程序保留在包main中。

有什么方法可以实现并且仍将程序保留在main包中吗?

Offtopic:是的,我知道通常您不希望将所有内容都导入到您的命名空间中,但是我们广泛使用了此模块,并且我们不想键入A::(实际上,模块名更长了一点)这并不能使情况变得更好)在成千上万的电话面前

3 个答案:

答案 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::'.$_;
        }
    }
}