我将使用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
语句引入的命名空间?
答案 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)
这里有两个问题:
问题2的一般答案是:你没有,因为任何编译单元都可能包含任意数量的包。
无论如何,这里有三种可能的解决方案:
最简单的解决方案是将所有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.pl
和Module/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 ) {
}