【问题标题】:How to conditionally import functions from another module and export them to the local namespace如何有条件地从另一个模块导入函数并将它们导出到本地命名空间
【发布时间】:2017-09-29 03:44:56
【问题描述】:

假设我有一个名为Local 的模块,它通过%EXPORT_TAGS 接口导出一个子例程subLocal

此模块与另一个名为 Remote 的模块密切相关,后者定义了 Local 的用户可能想要导入的子例程。

我有两个要求:

  1. 只有当模块Local 的用户正在导入Remote 中定义的子例程时,模块Local 才应该导入Remote 中定义的子例程(通过显式命名导出或使用特定导出标签)

  2. 1234563当引用Local 中定义的子程序时。

我只找到了 req. 2 通过在符号表中添加一个条目,但这种情况总是发生——不管Local 的用户是否真的需要Remote 中的子例程。根据 perldoc,这毫无意义地“污染”了命名空间。

那么在编译或运行时的什么时候我应该尝试从Remote 导入子例程?以及如何以它们出现在本地命名空间中的方式实际导入它们?

这是我目前的方法。模块Local:

package Local;

use strict;
use warnings;

BEGIN
{
  require Exporter;

  our @ISA = qw| Exporter |;

  our @EXPORT_LOCAL  = qw| subLocal |; 
  our @EXPORT_REMOTE = qw| subRemote |;

  our @EXPORT_OK   = ( @EXPORT_LOCAL, @EXPORT_REMOTE );
  our %EXPORT_TAGS = 
    ( all => \@EXPORT_OK, local => \@EXPORT_LOCAL, remote => \@EXPORT_REMOTE );

  *subRemote  = \&Remote::subRemote; # <-- can I do this conditionally somewhere? 
                                     # <-- and is there a better way to put this function in the user's local namespace?
}

use Remote; # <-- can I do this conditionally somewhere?

sub subLocal { return "(local)" }

1;

还有模块Remote

package Remote;

use strict;
use warnings;

BEGIN
{
  require Exporter;

  our @ISA = qw| Exporter |;

  our @EXPORT_REMOTE = qw| subRemote |;

  our @EXPORT_OK   = ( @EXPORT_REMOTE );
  our %EXPORT_TAGS = 
    ( all => \@EXPORT_OK, remote => \@EXPORT_REMOTE );
}

sub subRemote { return "(remote)" }

1;

【问题讨论】:

  • 这对我来说听起来像是一个循环定义。它读起来好像你想1)mainRemote导入的标识符导入Local,然后2)导入mainLocalRemote 导入的标识符。那是对的吗?如果是这样,那么这听起来像是糟糕的设计,我认为您需要举个例子。
  • 你可能想实现你自己的import
  • 您将无法使用 Exporter。您需要实现自己的 import,或者其他导出管理模块之一可能会有所帮助。
  • @Borodin 不,我只是想实现您的声明 2)。但增加了使该导入有条件的要求
  • 说实话,我认为对你来说最好的解决方案就是忘记污染你的命名空间!您不打算拥有数百个子例程吗?

标签: perl perl-module perl-exporter


【解决方案1】:

为什么要将潜艇导入本地被要求导出的本地潜艇?不如把它们直接放到正确的模块而不是Local!

无论哪种方式,您都无法(仅)使用 Exporter。您可以使用现有的 Exporter 替代方案。否则,您需要编写自己的import

本地.pm:

package Local;

use strict;
use warnings;

use Carp         qw( croak );
use Exporter     qw( );
use Import::Into qw( );
use Remote       qw( );

my @export_ok_local  = qw( subLocal );
my @export_ok_remote = qw( subRemote );
my @export_ok_all    = ( @export_ok_local, @export_ok_remote );

my %export_tags = (
   ':ALL'     => \@export_ok_all,
   ':DEFAULT' => [],
   ':local'   => \@export_ok_local,
   ':remote'  => \@export_ok_remote,
);

our @EXPORT_OK = @export_ok_local;

sub import {
   my $class = shift;
   my $target = caller;

   my @imports =
      map {
         !/^:/
            ? $_
            : !$export_tags{$_}
               ? croak("\"$_\" isn't a recognized tag")
               : @{ $export_tags{$_} }
      }
         @_;

   my %imports = map { $_ => 1 } @imports;

   my @local  = grep { $imports{$_} } @export_ok_local;
   my @remote = grep { $imports{$_} } @export_ok_remote;

   delete @imports{ @local, @remote };
   my @unknown = keys(%imports);
   croak("Not exported by ".__PACKAGE__.": @unknown\n") if @unknown;

   Remote->import::into($target, @remote);

   @_ = ( $class, @local );
   goto &Exporter::import;
}

sub subLocal { print("subLocal\n"); }

1;

远程.pm:

package Remote;

use strict;
use warnings;

use Exporter qw( import );

our @EXPORT_OK = qw( subRemote );

sub subRemote { print("subRemote\n"); }

1;

测试:

$ perl -e'
    use Local qw( subLocal subRemote );
    subLocal();
    subRemote();
'
subLocal
subRemote

$ perl -e'
    use Local qw( :ALL );
    subLocal();
    subRemote();
'
subLocal
subRemote

简单地导入您想要导出的所有内容要简单得多。

package Local;

use strict;
use warnings;

use Exporter qw( import );    

my ( @EXPORT_LOCAL, @EXPORT_REMOTE );
BEGIN {
  @EXPORT_LOCAL  = qw| subLocal |; 
  @EXPORT_REMOTE = qw| subRemote |;

  our @EXPORT_OK = ( @EXPORT_LOCAL, @EXPORT_REMOTE );

  our %EXPORT_TAGS = (
    ALL    => \@EXPORT_OK,
    local  => \@EXPORT_LOCAL,
    remote => \@EXPORT_REMOTE,
  );
}

use Remote @EXPORT_REMOTE;

sub subLocal { ... }

1;

【讨论】:

  • 这绝对是我脑海中的想象,并且可能最好地回答了这个问题,但我想我必须同意每个人都认为它有点愚蠢的设计
  • 一点也不。模块导出在其他模块中找到的东西是很常见的。就是先导入它来解决它。然后它就变成了对 Exporter 的微不足道的使用(如我的答案底部所示)。
【解决方案2】:

老实说,我认为与import 混淆所造成的混乱可能比命名空间污染更成问题,这只有在您选择与导入的标识符冲突的标识符时才会成为问题

这是一个使用面向对象设计的示例,它根本不使用import,并且命名空间污染为零。您甚至不必在主程序中说明您将使用哪些方法

远程.pm

use 5.010;

package Remote;

sub new {
    my $class = shift;

    my $self = bless {}, $class;
}

sub subRemote {
    say "I am subRemote";
}

1;

Local.pm

use 5.010;

package Local;

use base 'Remote';

sub new {
  my $class = shift;

  my $self = $class->SUPER::new(@_);
}

sub subLocal {
    say "I am subLocal";
}

1;

main.pl

use 5.010;

use Local;

my $obj = Local->new;

$obj->subLocal;
$obj->subRemote;

输出

I am subLocal
I am subRemote

【讨论】:

    【解决方案3】:

    我认为问题是:Local 的调用者可以在其导入列表中要求 subRemote,但如果不是,则符号不会被推送到调用者的命名空间中。

    我还假设Local 根本不应该从Remote 导入,除非Local 的调用者在其导入列表中需要Remote 的一些子项。

    然后写你自己的sub import。调用者提供的列表是传递给import 的参数,在第一个参数__PACKAGE__ 之后(在本例中为Local)。

    然后在您的import 中,您可以检查是否要求subRemote。如果是,require 定义它的包并将其子的全名推送到调用者的符号表,否则不。您可以建立并检查您可能需要的任何其他条件。

    只有当Local 的调用者需要来自Remote 的子时,Local 才会加载 Remote


    上面描述的一个例子

    Local.pm

    package Local;
    
    use warnings;
    use strict;
    use Exporter qw();
    
    our @EXPORT_OK = qw(subLocal subRemote);
    
    sub import {
        my $class = shift;
    
        my $re = qr/^(?:subRemote|other)/;
        my @local_exports  = grep { !/$re/ } @_;
        my @remote_exports = grep {  /$re/ } @_;   # check both
    
        if (@remote_exports) {   
            no strict 'refs';
            require Remote;
            foreach my $export (@remote_exports) 
            {   
                my $to_caller = caller() . '::' . $export;
    
                *{ $to_caller } = \&{ 'Remote::' . $export };
            }   
        }   
    
        @_ = ($class, @local_exports);  # set up @_ for goto
        goto &Exporter::import;         # switch to Exporter::import
    }
    
    sub subLocal {  print "subLocal() in ", __PACKAGE__, "\n" }
    
    1;
    

    请求的来自Remote 的subs 的引用被写入调用者的符号表。然后我们的importExporter::import 交换,用于从Local 导出其余的符号。例如,关于 goto see this 的注释。遗漏了一些事情,首先检查收到的导入列表。

    mainRemote 没有惊喜

    ma​​in.pl

    use warnings;
    use strict;
    use Local qw(subLocal subRemote);
    
    subLocal();
    subRemote();
    

    Remote.pm

    package Remote;   
    use warnings;
    use strict;
    use Exporter qw(import);
    
    our @EXPORT_OK = qw(subRemote);
    
    sub subRemote { print "subRemote() in ", __PACKAGE__, "\n" }
    

    输出

    本地的 subLocal() 远程中的 subRemote()

    这完成了所要求的,但它必须处理相当具体的细节。

    【讨论】:

    • 并将其推送到调用者的符号表中完全按照我目前的做法完成(即*subRemote = \&amp;Remote::subRemote;)?
    • @ardnew 嗯,没错——但是当您编写自己的import 时,您可以有条件地 这样做。那是你要的吗?在答案中完成了该陈述。顺便说一句,你没有导出到 caller 的命名空间..
    • @ardnew:我希望你能多解释一下。每个人都在猜测你的意思。听起来你只需要export_to_level
    • @ardnew 我更新了答案以更准确地说明我现在认为您需要什么。
    • @ardnew 你得到了非常好的答案,但我意识到我误读了你的第一条评论,关于如何写入调用者的符号表。除了这一行,还有很多事情要做,还有基础知识:my $to_caller = caller() . '::subRemote'; *{ $to_caller } = \&amp;Remote::subRemote;。 RHS 引用 sub,LHS 是写入它的 glob 中的插槽。如果从main:: 调用,LHS 的结果为*main::subRemote = \&amp;Remote::subRemote;,但您需要caller 才能动态找到它,因此需要{} 等。再次,只是为了完整性..
    猜你喜欢
    • 1970-01-01
    • 2018-07-25
    • 2017-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-24
    • 2022-12-10
    • 2018-01-05
    相关资源
    最近更新 更多