需要使用别名的Perl模块

时间:2019-04-25 01:20:43

标签: 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;
[...]

消除冗余

正如我提到的,有很多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相关):

  • perldoc for constant中,它表示常量列表实际上不是只读的。使用eval "require $x"是否会引起安全隐患?
  • 如果是这样,是否有更安全的方法无需加载其他模块?
  • 对于Perl来说,有些陌生,这种方法和我以前的方法(每个模块各自的eval语句)之间可能还有其他细微的区别吗?

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

5 个答案:

答案 0 :(得分:3)

  

是否有其他别名Foo :: Bar :: Baz :: as Things ::这样我可以要求Things :: ThingA吗?

是的。要满足此要求,有两个要求:

  1. 像已经完成的那样创建包别名。

    BEGIN { *Things:: = *Foo::Bar::Baz:: }
    
  2. 从您的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")