需要使用别名的Perl模块

Bry*_*Kan 6 perl

***以下是背景信息,以帮助解释到目前为止我已经尝试过的内容。如果您想先阅读主要问题,请跳至底部。***


起步

我的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;
[...]
Run Code Online (Sandbox Code Playgroud)

消除冗余

正如我提到的,有很多Thing模块,并且我仍在添加更多模块。每次创建新Thing类时,都必须添加新require语句,并将相同的相同文本添加到ALL_THINGS列表中。为了避免这种重复,我想require用循环遍历的循环替换各个行ALL_THINGS。我添加了它,它本身可以正常工作:

foreach my $module (ALL_THINGS) {
    eval "require $module";
}
Run Code Online (Sandbox Code Playgroud)

但是,此解决方案似乎不适用于我的下一个更改。


提高可读性

每个模块的完整模块名称Thing很长而且很笨拙。我想给软件包名称起别名,以使其易于输入/阅读。我看着Package::Alias,但似乎会use,如果可能的话,我想避免。到目前为止,我得出的最佳解决方案是此问题中建议的模式:

BEGIN { *Things:: = *Foo::Bar::Baz:: ; }
Run Code Online (Sandbox Code Playgroud)

从允许我使用的意义上讲,这也有效Thing::ThingA->classMethod。但是,毫不奇怪,它不适用于require上面的循环,因为它require Thing::ThingA搜索而不是。@INCThing/ThingA.pmFoo/Bar/Baz/ThingA.pm


主要问题:将它们放在一起

我想将列表中的长包名(即Foo::Bar::Baz::ThingA)缩减ALL_THINGSThings::ThingA,但仍然可以使用该列表require在循环中构建我的语句。

  • 是否有不同的方式,以别名Foo::Bar::Baz::作为Things::这样我可以require Things::ThingA
  • 或者,如果我正确地执行别名部分,是否Things::ThingA可以Foo::Bar::Baz::ThingA在eval中(或之前)取消引用,以便require找到正确的软件包?
  • 是否有其他普遍接受的方法可以将同一名称空间的不同级别的程序包捆绑在一起,以消除所有这些需求?

奖励问题(与有关eval "require $x"):

  • perldoc中,常量表示常量列表实际上不是只读的。使用会带来安全隐患eval吗?
  • 如果是这样,是否有一种更安全的方法无需加载其他模块?
  • 对于Perl来说,它是新手,这种方法与我以前的方法(require每个模块的单独说明)之间可能还有其他细微的区别吗?

注意:我已接受Dave Sherohman的回答,因为它可以最全面地解决我提出的问题。但是,我最终根据lordadmira的答案实施了一个解决方案。

Dav*_*man 3

你喜欢你的魔法有多黑?

我们都知道,为了require模块,Perl 会遍历@INC查找它想要加载的文件。此过程鲜为人知(甚至更少使用)的方面之一是它@INC不仅限于包含文件系统路径。您还可以将 coderef 放在那里,从而允许您劫持模块加载过程并使其按照您的意愿进行。

对于您描述的用例,类似以下内容(未经测试)应该可以解决问题:

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;  
}
Run Code Online (Sandbox Code Playgroud)

基本上,它的作用是,作为 中的第一个条目@INC,它会查看所请求模块的名称,如果它以 开头Thing::,则会加载相应的Foo::Bar::Baz::模块。简单有效,但容易让未来的维护程序员(包括你自己!)感到困惑,所以要谨慎使用。


作为替代方法,您还可以选择在模块中指定与文件的物理路径不对应的包名称 - 按照惯例,两者通常是相同的,以便在阅读和维护代码时更轻松,但它们没有匹配的技术要求。如果文件./lib/Foo/Bar/Baz/Xyzzy.pm包含

package Thing::Xyzzy;

sub frob { ... };
Run Code Online (Sandbox Code Playgroud)

那么你可以通过这样做来使用它

require Foo::Bar::Baz::Xyzzy;
Thing::Xyzzy::frob();
Run Code Online (Sandbox Code Playgroud)

Perl 会对此非常满意(尽管你的同事可能不会)。


最后,如果您想摆脱ALL_THINGS,请查看Module::Pluggable。您给它一个名称空间,然后它会查找该名称空间中的所有可用模块并为您提供它们的列表。也可以将其设置为require找到的每个模块:

use Module::Pluggable require => 1, search_path => ['Foo::Bar::Baz'];
my @plugins = plugins;
Run Code Online (Sandbox Code Playgroud)

@plugins现在包含所有模块的列表Foo::Bar::Baz::*,并且这些模块已经加载了require. 或者,plugins如果您只关心加载模块并且不需要它们的列表,则可以直接调用而不将结果分配给变量。