【问题标题】:Perl: Dynamically loading modules and accessing the exported contentsPerl:动态加载模块并访问导出的内容
【发布时间】:2012-01-13 18:50:49
【问题描述】:

在研究了 Perl require docsStackoverflow 上的其他类似链接之后,我仍然没有更聪明,一定是错过了一个相当简单的技巧。我在运行时加载一个模块并在其中调用一个子例程。问题是我不一定知道导出的子程序的名字,但一定有,而且是导出的。

所有模块看起来都是这样的,即。他们大致遵循来自perlmonks.org的模板

package modules::Test;

use strict;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);

$VERSION     = 1.00;
@ISA         = qw(Exporter);
@EXPORT      = (*TestSubSomeUnknownName);
@EXPORT_OK   = qw(&TestSubSomeUnknownName);
%EXPORT_TAGS = ( ALL => [qw(&TestSubSomeUnknownName)]
               );


sub TestSubSomeUnknownName
{
    # return a hash reference
}

然后我可以像这样访问 sub,假设我知道它的名字:

use Module::Load;

my $package = "modules::Test";
my $subr = "TestSubSomeUnknownName";

load $package;
# Call the subroutine
my $hashref = $package->$subr;

但是如果有人在包裹中拼错了名字或者我不知道怎么办?解决方案似乎是使用其中一个 EXPORT 来查看其中的内容,但怎么做呢?

【问题讨论】:

标签: perl dynamic module load package


【解决方案1】:
  • @EXPORT 应该包含 names 而不是符号。
  • 你不需要在函数名前加上 & 符号,它是非标准的。
  • 记住在模块末尾返回1
  • 如果您想检查一个包可以做什么,请使用can

    die 'Auto-import sub was not named "TestSubSomeUnknownName"' 
        unless  $package->can( 'TestSubSomeUnknownName' )
        ;
    $package->TestSubSomeUnknownName(); 
    

【讨论】:

    【解决方案2】:

    您可以尝试@modules::Test::EXPORT 包含的内容。另外,尝试运行$modules::Test::EXPORT[0]->()

    【讨论】:

    • +1 为什么这被否决了?这是接近手头问题的唯一答案。 (当然,最初的问题有点糊涂 - 并且有问题的模块将 @EXPORT 看起来是类方法的事实是可悲的错误 - 但 OP 对@的重新散列感兴趣987654323@.)
    • 这几乎可以回答我的问题,但花了一段时间才弄清楚如何将模块名称删除到变量中。
    【解决方案3】:

    你是:

    • 编写模块并想知道如何导出子程序?
    • 您是否正在查看一个模块并希望使用其中的子例程?

    对于第一个问题的答案,答案通常是不要自动导出任何东西。如果在@EXPORT_OK 列表下放置一个子程序名称列表,则可以在主程序中使用以下语法导出这些子程序:

     use My::Module qw(subroutines to be imported);
    

    如果你想自动导入一个子程序,你可以把它放在@EXPORT列表中。这些将被自动导入,必须以File::Copy 自动导入复制子程序的方式。

    现在,现代 Perl 标准不赞成导出任何内容,因为它污染用户的命名空间,而不必通知用户。如果你真的,真的想导入一些东西,现在的标准是使用@EXPORT_OK,所以用户必须在他们的use语句中列出他们想要导入的子程序。这至少记录了污染。

    File::Spec 这样的一些现代模块什么都不导入。您要么必须在子例程前加上模块名称,要么使用面向对象语法(即使像File::Spec,它也不是真正的面向对象,因为没有要定位的对象。)

    这给我们带来了另一件事:在您的模块中使用面向对象的 Perl。然后,您不必担心导出任何内容,因为您不在 OOPerl 中。

    如果您尝试做第二个问题并且只是尝试从第三方模块中查找子例程的名称,请使用perldoc 命令并查看文档。您可以查看@EXPORT@EXPORT_OK 列表以查看导出的内容,但可能有一些未导出的内容(如File::Find 模块中的$File::Find::Name 变量)可能很重要。

    如果你真的想搞砸,你可以尝试像这样遍历每个包变量:

    #! /usr/bin/env perl
    use strict;
    use warnings;
    use feature qw(say);
    
    while (my ($var, $type) = each %Foo::) {
        if (defined &$type) {
            say "$var is a subroutine";
        }
        else {
            say "$var is defined as something or another";
        }
    }
    
    package Foo;
    
    our %bar = (foo => 1, bar => 2);
    our @foo = qw(foo bar);
    our $fubar = "barfu";
    
    sub barfu {
        print "FOO!";
        return "FOO!";
    }
    

    这将遍历包,向您展示定义的内容,甚至告诉您它是否是子例程。我得到以下输出:

    barfu is a subroutine
    fubar is defined as something or another
    bar is defined as something or another
    foo is defined as something or another
    

    除了定义的子例程之外,我无法弄清楚如何查看它是什么类型的变量。也许其他人可以帮我弄清楚。我可能不得不对eval 做点什么。


    感谢 Eric Storm 的修改

    下面将显示它的类型。不幸的是,这会说一切都是标量和球体。 Glob 是可以理解的(它是一个 glob)但是一个标量?我想暂时将这些类型排除在 for 循环之外。

    #! /usr/bin/env perl
    use strict;
    use warnings;
    use feature qw(say);
    use Data::Dumper;
    
    while (my ($var, $type) = each %Foo::) {
        print "$var";
    #   foreach my $ref_type (qw(SCALAR GLOB ARRAY HASH CODE REF LVALUE FORMAT IO VSTRING Regexp)) {
        foreach my $ref_type (qw(ARRAY HASH CODE REF LVALUE FORMAT IO VSTRING Regexp)) {
            if (defined *$type{$ref_type}) {
                say qq("$var" is a type $ref_type);
            }
        }
    }
    
    package Foo;
    
    our %bar = (foo => 1, bar => 2);
    our @foo = qw(foo bar);
    our $fubar = "barfu";
    
    sub barfu {
        print "FOO!";
        return "FOO!";
    }
    

    【讨论】:

    • 每个 $type 都是一个 glob 引用,因此 *$type{REF_TYPE} 可以获取 glob 中每个元素的引用。
    • @EricStrom - 谢谢,我修改了我的代码以根据perldoc -f ref 搜索所有各种类型。它很好地指出了子程序(CODE)、HASH和ARRAY。但是,每一个也是对 SCALAR 和 GLOB 的引用。我了解 GLOB,但为什么是 SCALAR?
    【解决方案4】:

    所以最后我回到这个问题 - 也许我有点含糊,但我遇到的问题是动态加载模块,并通过仅查看动态加载的模块来找出从这些模块中导出的内容。

    经过一番修改后,以下工作达到目的:

    my $package = 'modules::Test';
    load $package;
    my $exports; 
    eval {
        no strict;
        $exports = \@{"$package\::EXPORT"};
    };
    # Call the subroutine
    my $subr = @{$exports}[0];
    my $hash = $package->$subr if($package->can($subr));
    

    要加载的包的名称只是一个字符串,包已加载,$exports 行(从 Exporter.pm 复制,我发现单步执行 $_->import)返回导出符号列表,然后我可以打电话给第一个。

    如果有人可以进一步改进,我很感兴趣。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多