2

我将使用该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

4 回答 4

5

哇。我不得不说这是我最近看到的最有趣的 Perl 问题之一。从表面上看,这似乎是一个非常简单的请求——获取一个包含模块的命名空间,但实际上没有办法做到这一点。您可以在包装中获取它,但不能从包装外部获取。我尝试使用 EXPORT 将本地包名称发送回调用者脚本,但考虑到“使用”和“要求”工作方式的不同,这最终无济于事。更多模块类型的方法可能会与“use”语句一起使用,但所需脚本能够自行运行的要求阻止了这种方法。剩下要做的就是直接污染调用者'

顺便说一句 - 我不敢相信这真的有效 - 在严格模式下,不少于。

调用者.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
于 2013-10-18T17:17:35.507 回答
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";

您也可以只解析 perl 文件中的“包”声明。实际上,这就是 PAUSE 上传守护进程正在做的事情,所以对于大多数情况来说它可能“足够好”。查看https://github.com/andk/pause/blob/master/lib/PAUSE/pmfile.pmpackages_per_pmfile中 的子程序

于 2013-10-18T20:52:28.213 回答
1

这里有两个问题:

  1. 当作为独立执行和作为模块使用时,如何更改脚本的行为?
  2. 如何发现我刚刚编译的一段代码的包名?

问题 2 的一般答案是:您不需要,因为任何编译单元都可能包含任意数量的包。

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

  1. 命名您的模块,以便您在加载它时已经知道名称。
  2. 让每个模块在一个中心集合点注册自己。
  3. 与 #1 类似,但添加了插件的自动发现功能。

最简单的解决方案是将所有 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尽管这样更清楚。如果你想有条件地运行一个模块中的代码,只有当它被用作脚本时,你可以利用这个unless(caller)技巧


当然,所有这些都是骗人的:这里我们根据模块名称确定文件名,而不是反过来——正如我已经提到的,我们不能这样做。

我们可以做的是让每个模块在某个预定义的位置注册自己,例如通过

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();
  }
}

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

于 2013-10-18T18:53:56.683 回答
0

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

就像 Rick 之前说的,必须侵入调用者的命名空间或者更好的“main”。我更喜欢在“必需”包的 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 ) {

}
于 2016-06-17T11:31:53.213 回答