***以下是帮助解释到目前为止我尝试过的内容的背景。如果您想先阅读主要问题,请跳至底部。***
我的Baz
模块调用了许多其他模块,所有这些模块都相似,每个模块在名称空间中都向下一层。这里感兴趣的人组成Thing
角色。除了单独的require
语句之外,常量列表ALL_THINGS
还会列举相关的Thing
模块,以便以后使用。我的原始代码如下:
package Foo::Bar::Baz;
use constant ALL_THINGS => qw{ Foo::Bar::Baz::ThingA Foo::Bar::Baz::ThingB ... };
require Foo::Bar::Baz::ThingA;
require Foo::Bar::Baz::ThingB;
[...]
正如我提到的,有很多Thing
模块,并且我仍在添加更多模块。每次创建新的Thing
类时,都必须添加一个新的require
语句,并将相同的相同文本添加到ALL_THINGS
列表中。为了避免重复,我想用在require
上循环的循环来替换单独的ALL_THINGS
行。我添加了它,它本身可以正常工作:
foreach my $module (ALL_THINGS) {
eval "require $module";
}
但是,这种解决方案似乎不适用于我的下一个更改。
每个Thing
的完整模块名称很长而且很笨拙。我想给软件包名称起别名,以使其易于输入/阅读。我看着Package::Alias
,但似乎会use
,如果可能的话,我想避免。到目前为止,我得出的最佳解决方案是this question中建议的模式:
BEGIN { *Things:: = *Foo::Bar::Baz:: ; }
在允许我使用Thing::ThingA->classMethod
的意义上,这也起作用。但是,毫不奇怪,它在上面的require
循环中不起作用,因为require Thing::ThingA
在@INC
中搜索Thing/ThingA.pm
而不是Foo/Bar/Baz/ThingA.pm
。
我想将Foo::Bar::Baz::ThingA
列表中的长软件包名称(即ALL_THINGS
)缩减为Things::ThingA
,但是仍然可以使用相同的列表来构建我的{ {1}}个循环语句。
require
别名为Foo::Bar::Baz::
的方式,以便我可以Things::
?require Things::ThingA
解除对Things::ThingA
的引用,以便Foo::Bar::Baz::ThingA
找到正确的包装? 奖励问题(与require
相关):
eval "require $x"
是否会引起安全隐患? eval
语句)之间可能还有其他细微的区别吗?注意:我接受了Dave Sherohman的回答,因为它可以最全面地解决我提出的问题。但是,我最终根据lordadmira的答案实施了一个解决方案。
答案 0 :(得分:3)
是否有其他别名Foo :: Bar :: Baz :: as Things ::这样我可以要求Things :: ThingA吗?
是的。要满足此要求,有两个要求:
像已经完成的那样创建包别名。
BEGIN { *Things:: = *Foo::Bar::Baz:: }
从您的mylibs/Things
目录(其中mylibs/Foo/Bar/Baz
是您的Perl模块的路径)创建指向mylibs
的符号链接
(如果需要,也可以从Foo/Bar/Baz.pm
文件链接到Things.pm
)
完成此操作并调用eval "require Things::Quux"
或eval "use Things::Quux"
之后,Perl会将文件加载到mylibs/Things/Quux.pm
中,该文件与mylibs/Foo/Bar/Baz/Quux.pm
文件相同。该文件中有一个package Foo::Bar::Baz::Quux
语句,但是由于该包已经被别名为Things::Quux
命名空间,因此可以在任一命名空间中访问其所有子例程和包变量。
在同一名称空间的不同级别上是否还有其他将包捆绑在一起的方法,以消除所有这些需求?
不清楚您的对象模型是什么,但是如果*::Thing1
,*::Thing2
等都是某个通用基类的所有实现,则可以在基类中考虑一个工厂方法。
package Foo::Bar::Baz;
sub newThing {
my ($class, $implementation, @options) = @_;
eval "use $class\::$implementation; 1"
or die "No $implementation subclass yet";
no strict 'refs';
my $obj = "$class\::$implementation"->new(@options);
return $obj;
}
现在Foo::Bar::Baz::Thing7
(可能是别名,也可能不是别名Things::Thing7
)仅在需要时才加载,例如从类似的呼叫中加载
my $obj7 = Foo::Bar::Baz->newThing("Thing7",foo => 42);
print ref($obj7); # probably Foo::Bar::Baz::Thing7
答案 1 :(得分:3)
您喜欢魔术多黑?
我们都知道,为了使用require
模块,Perl浏览@INC
来查找要加载的文件。此过程的鲜为人知(甚至很少使用)的方面之一是@INC
不仅限于仅包含文件系统路径。您还可以在此处放置代码引用,从而劫持模块加载过程并将其按自己的意愿弯曲。
对于您描述的用例,应使用以下(未经测试的)方法:
BEGIN { unshift @INC, \&require_things }
sub require_things {
my (undef, $filename) = @_;
# Don't go into an infinite loop when you hit a non-Thing:: module!
return unless $filename =~ /^Thing::/;
$filename =~ s/^Thing::/Foo::Bar::Baz::/;
require $filename;
}
基本上,这是作为@INC
中的第一项,它查看所请求模块的名称,并且如果它以Thing::
开头,则会加载相应的Foo::Bar::Baz::
模块。简单有效,但确实容易使将来的维护程序员(包括您自己!)感到困惑,因此请谨慎使用。
作为一种替代方法,您还可以选择在模块中指定与文件的物理路径不对应的程序包名称-按照惯例,两者通常是相同的,以使阅读和阅读时更容易维护代码,但是对它们没有匹配的技术要求。如果文件./lib/Foo/Bar/Baz/Xyzzy.pm
包含
package Thing::Xyzzy;
sub frob { ... };
然后您将通过使用它
require Foo::Bar::Baz::Xyzzy;
Thing::Xyzzy::frob();
Perl将对此感到非常满意(即使您的同事可能不满意)。
最后,如果您想摆脱ALL_THINGS
,请看看Module::Pluggable。您给它一个名称空间,然后它找到该名称空间中的所有可用模块,并提供它们的列表。也可以将其设置为require
每个找到的模块:
use Module::Pluggable require => 1, search_path => ['Foo::Bar::Baz'];
my @plugins = plugins;
@plugins
现在包含所有Foo::Bar::Baz::*
模块的列表,并且这些模块已经加载了require
。或者,如果您只关心加载模块并且不需要它们的列表,则可以直接调用plugins
而不将结果分配给变量。
答案 2 :(得分:2)
在这里使用typeglob进行欺骗就像是核杀伤力过大。 Module :: Runtime是基于配置数据在运行时加载模块的标准方法。此时,一切都可以是普通变量。在这里使用常量没有好处。
这是我在IRC聊天中的建议。
package Foo::Bar::Baz;
use strict;
use Module::Runtime "require_module";
use List::Util "uniq";
my $prefix = "Things::LetterThings";
my %prop_module_map = (
foo => [ qw{ThingC ThingF ThingY} ],
bar => [ qw{ThingL ThingB} ],
baz => [ qw{ThingA ThingB ThingC ThingE ThingG ThingH ThingZ} ],
# or
# ALL => [ qw{ThingA .. ThingZ} ],
);
my @all_modules = uniq map { @$_ } values %prop_module_map;
sub load_modules {
my $self = shift;
# map module list if matching property found, otherwise use ALL_MODULES
my $modules = $prop_module_map{$self->prop} ?
$prop_module_map{$self->prop} :
\@all_modules;
#only do the map operation on the list we actually need to use
my @modules = map { join "::", $prefix, $_ } @$modules;
foreach my $module (@modules) {
require_module($module);
}
}
1;
__END__
答案 3 :(得分:0)
我根据perlmod中的示例提出了一个似乎可行的解决方案,尽管我仍在努力解决。我发布此邮件是希望有人可以对其进行改进,或者至少要对其进行解释/提供反馈。
foreach my $package (ALL_THINGS) {
no strict "refs";
$package = *{$package}{PACKAGE}."::".*{$package}{NAME};
eval "require $package";
}
编辑:在转到perlref的链接之后,我在第(7)节中发现了这个问题:
* foo {NAME} 和 * foo {PACKAGE} 是例外,因为它们返回字符串,而不是引用。它们返回typeglob本身的包和名称,而不是已分配给它的名称。因此,在 * foo = * Foo :: bar 之后, * foo 在用作字符串时将变为“ * Foo :: bar” ,但 * foo {PACKAGE} 和 * foo {NAME} 将分别继续生成“ main”和“ foo”。
由此,*Things{PACKAGE}
将始终解析为Foo::Bar::Baz
是有意义的,因为这是我们正在使用的程序包,因此是typeglob“所属”的程序包。在上面的代码中,$package
解析为Things::ThingA
,而不是Things
,因此我们得到*Things::ThingA{PACKAGE}
。同样,第二部分变为*Things::ThingA{NAME}
。我可能会冒险猜出为什么这可能起作用,但事实是我不确定。
答案 4 :(得分:0)
主要使用NAME和PACKAGE,以便您可以找到引用的包和名称。除此之外,这是一种返回变量名称而不是变量值的方法,例如调试。
printf "%s\n", __PACKAGE__; my $y = \*ydp; pp *$y{PACKAGE}, *$y{NAME};
W
("W", "ydp")