从require'd perl脚本访问subs

时间:2013-10-18 15:20:11

标签: perl namespaces

我将使用require语句导入一些perl代码。我要导入的代码位于mylibA.pl

#!/usr/bin/perl
package FOO::BAR;

sub routine {
    print "A message!\n";
}

mylibB.pl

#!/usr/bin/perl
package FOO::BAZ;

sub routine {
    print "Another message!\n";
}

然后我会像这样使用它:

#!/usr/bin/perl
foreach my $lib (qw/ mylibA.pl mylibB.pl /){
     require $lib;
     print "Make a call to ${lib}'s &routine!\n";
}

我的脚本是否有办法找出使用require语句引入的命名空间?

4 个答案:

答案 0 :(得分:5)

哇。我不得不说这是我在一段时间内看到的最有趣的Perl问题之一。从表面上看,这似乎是一个非常简单的请求 - 获取一个包含模块的命名空间,但实际上没有办法做到这一点。您可以在包装中获得它,但不能从包装外部获得。我尝试使用EXPORT将本地程序包名称发送回调用程序脚本,但由于“使用”和“需要”工作方式不同,最终无处可去。更多模块类型的方法可能会使用“use”语句,但所需脚本能够自行运行的要求阻止了这种方法。剩下要做的唯一事情是直接污染调用者的命名空间并希望最好(假设调用者没有包命名空间) - 这是模块旨在防止的东西。

顺便说一句 - 我无法相信这确实有效 - 在严格模式下,不能少。

caller.pl

#!/usr/bin/perl
use strict;

#package SomePackageName; #if you enable this then this will fail to work

our $ExportedPackageName;

print "Current package=".__PACKAGE__."\n";

foreach my $lib (qw/ mylibA.pl mylibB.pl /){
    require $lib;
    print "Make a call to ${lib}'s &routine!\n";
    print "Package name exported=".$ExportedPackageName."\n";
    $ExportedPackageName->routine;
} #end foreach

print "Normal Exit";
exit;

__END__

mylibA.pl

#!/usr/bin/perl
package FOO::BAR;
use strict;

#better hope the caller does not have a package namespace
$main::ExportedPackageName=__PACKAGE__;

sub routine {
    print "A message from ".__PACKAGE__."!\n";
}

1;

mylibB.pl

#!/usr/bin/perl
package FOO::BAZ;
use strict;

#better hope the caller does not have a package namespace
$main::ExportedPackageName=__PACKAGE__;

sub routine {
    print "Another message, this time from ".__PACKAGE__."!\n";
}

1;

结果:

c:\Perl>
c:\Perl>perl caller.pl
Current package=main
Make a call to mylibA.pl's &routine!
Package name exported=FOO::BAR
A message from FOO::BAR!
Make a call to mylibB.pl's &routine!
Package name exported=FOO::BAZ
Another message, this time from FOO::BAZ!
Normal Exit

答案 1 :(得分:2)

关于在perl源文件中查找包的主要学术问题:

您可以尝试使用CPAN模块Module::Extract::Namespaces来获取perl文件中的所有包。它使用的是PPI,因此不是100%完美,但大部分时间都足够好:

perl -MModule::Extract::Namespaces -e 'warn join ",", Module::Extract::Namespaces->from_file(shift)' /path/to/foo.pm

但对于大型文件,PPI可能会很慢。

您可以尝试比较require之前和之后的活动包。这也不完美,因为如果您的perl库文件加载了其他模块,那么您无法分辨出哪个是主文件包以及后来加载了什么。要获取可以使用的包列表,例如Devel::Symdump。这是一个示例脚本:

use Devel::Symdump;

my %before = map { ($_,1) } Devel::Symdump->rnew->packages;
require "/path/to/foo.pm";
my %after  = map { ($_,1) } Devel::Symdump->rnew->packages;

delete $after{$_} for keys %before;
print join(",", keys %after), "\n";

你也可以解析per#文件" package"声明。实际上,这是PAUSE上传守护进程正在做的事情,所以它可能已经足够好了#34;对于大多数情况。查看子例程packages_per_pmfile https://github.com/andk/pause/blob/master/lib/PAUSE/pmfile.pm

答案 2 :(得分:1)

这里有两个问题:

  1. 如何在作为独立模块执行时以及用作模块时更改脚本的行为?
  2. 如何发现我刚刚编译的一段代码的包名?
  3. 问题2的一般答案是:你没有,因为任何编译单元都可能包含任意数量的包。

    无论如何,这里有三种可能的解决方案:

    1. 为您的模块命名,以便在加载时知道该名称。
    2. 让每个模块在中央会合点注册。
    3. 与#1类似,但添加了插件的自动发现。
    4. 最简单的解决方案是将所有API放在普通模块中,并将独立逻辑放在一个单独的脚本中:

      /the/location/
        Module/
          A.pm
          B.pm
        a-standalone.pl
        b-standalone.pl
      

      每个独立基本上看起来像

      use Module::A;
      Module::A->run();
      

      如果另一个脚本想要重用该代码,那么

      use lib "/the/location";
      use Module::A;
      ...
      

      如果在运行时加载,那么Module::Runtime会对此有所帮助:

      use Module::Runtime 'use_module';
      use lib "/the/location";
      my $mod_a = use_module('Module::A');
      $mod_a->run();
      

      a-standalone.plModule/A.pm的内容放在单独的文件中并不是绝对必要的,尽管这样更清楚。如果您只想在模块中使用脚本时有条件地运行代码,则可以使用the unless(caller) trick


      当然所有这些都是诡计:在这里我们从模块名称确定文件名,而不是相反的方式 - 正如我已经提到过的那样我们做不到。

      我们可以做的是让每个模块在某个预定位置注册,例如由

      Rendezvous::Point->register(__FILE__ => __PACKAGE__);
      

      当然,独立版本必须屏蔽没有Rendezvous::Point的可能性,因此:

      if (my $register = Rendezvous::Point->can("register")) {
        $register->(__FILE__ => __PACKAGE__);
      }
      
      呃,这很愚蠢,违反DRY。因此,让我们创建一个Rendezvous::Point模块来处理这个问题:

      /the/location/Rendezvous/Point.pm

      package Rendezvous::Point;
      use strict; use warnings;
      
      my %modules_by_filename;
      
      sub get {
        my ($class, $name) = @_;
        $modules_by_filename{$name};
      }
      
      sub register {
        my ($file, $package) = @_;
        $modules_by_filename{$file} = $package;
      }
      
      sub import {
        my ($class) = @_;
        $class->register(caller());
      }
      

      现在,use Rendezvous::Point;注册调用包,模块名称可以通过绝对路径重试。

      现在想要使用各种模块的脚本可以:

      use "/the/location";
      use Rendezvous::Point ();  # avoid registering ourself
      
      my $prefix = "/the/location";
      for my $filename (map "$prefix/$_", qw(Module/A.pm Module/B.pm)) {
        require $filename;
        my $module  = Rendezvous::Point->get($filename)
                   // die "$filename didn't register itself at the Rendezvous::Point";
        $module->run();
      }
      

      然后有像Module::Pluggable这样功能齐全的插件系统。该系统通过查看Perl模块可能驻留的所有路径来工作,并在它们具有特定前缀时加载它们。解决方案如下:

      /the/location/
        MyClass.pm
        MyClass/
          Plugin/
            A.pm
            B.pm
        a-standalone.pl
        b-standalone.pl
      

      一切都与第一个解决方案一样:独立脚本看起来像

      use lib "/the/location/";
      use MyClass::Plugin::A;
      MyClass::Plugin::A->run;
      

      但是MyClass.pm看起来像:

      package MyClass;
      use Module::Pluggable require => 1;  # we can now query plugins like MyClass->plugins
      
      sub run {
        # Woo, magic! Works with inner packages as well!
        for my $plugin (MyClass->plugins) {
          $plugin->run();
        }
      }
      

      当然,这仍然需要特定的命名方案,但它会自动发现可能的插件。

答案 3 :(得分:0)

如前所述,如果没有额外的I / O,猜测或假设,就无法查找“必需”包的名称空间。

就像Rick之前说过的那样,必须要打扰调用者的命名空间或更好的“主要”。我更喜欢在'required'包的BEGIN块中注入特定的钩子。

#VENDOR/App/SocketServer/Protocol/NTP.pm
package VENDOR::App::SocketServer::Protocol::NTP;

BEGIN {
  no warnings;
  *main::HANDLE_REQUEST = \&HANDLE_REQUEST;
}

sub HANDLE_REQUEST {
}

#VENDOR/App/SocketServer.pm
my $userPackage= $ARGV[0];
require $userPackage;
main::HANDLE_REQUEST();

而不是* main ::你可以使用* main :: HOOKS :: HANDLE_REQUESTS获得更具体的信息,即这使你能够通过遍历HOOK的命名空间部分轻松解决调用者中所有注入的钩子。

foreach my $hooks( keys %main::HOOKS ) {

}