Perl:同一个文件中的两个包不能导入同一个包?

Posted

技术标签:

【中文标题】Perl:同一个文件中的两个包不能导入同一个包?【英文标题】:Perl : Two packages in same file cannot import same package? 【发布时间】:2013-01-17 12:14:14 【问题描述】:

这是一个有趣的 Perl 行为。 (至少对我来说:))

我有两个包 PACKAGE1PACKAGE2 导出具有相同名称的函数 Method1()

由于会有很多包导出相同的功能,use-ing Perl 文件中的所有内容都将是乏味的。所以,我创建了一个通用包含文件INCLUDES.pm,其中将包含这些uses。

包括.pm:

use PACKAGE1;
use PACKAGE2;

1;

PACKAGE1.pm:

package PACKAGE1;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw (
          Method1
);

sub Method1
print "PACKAGE1_Method1 \n";


1;

PACKAGE2.pm:

package PACKAGE2;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw (
    Method1
);

sub Method1
    print "PACKAGE2_Method1 \n";


1;

Tests.pl:

##################first package################
package Test1;
use INCLUDES;
my @array = values(%INC);
print  "@array  \n";

Method1();

##################second package################
package Test2;
use INCLUDES;  #do "INCLUDES.pm";
my @array = values(%INC);
print  "@array  \n";

Method1();

动机是,在任何 Perl 文件中只能使用最新包的 Method1()

输出让我感到惊讶。 我希望Tests.pl 中的两个Method1() 调用都应该成功。 但只有第一个 Method1() 执行,第二个 Method1() 调用显示“未定义”。

输出:

C:/Perl/site/lib/sitecustomize.pl PACKAGE1.pm C:/Perl/lib/Exporter.pm PACKAGE2
.pmINCLUDES.pm

PACKAGE2_Method1

C:/Perl/site/lib/sitecustomize.pl PACKAGE1.pm C:/Perl/lib/Exporter.pm PACKAGE2
.pm INCLUDES.pm

Undefined subroutine &Test2::Method1 called at C:\Temp\PackageSample\Tests.pl line 15.

有人对此有任何答案/看法吗?

实际场景:

多个 Perl 模块中的方法将具有相同的名称。但是只能使用高优先级 perl 模块中的方法。

例如,如果PACKAGE1 包含Method1(), Method2() & PACKAGE2 仅包含Method1(),则Method1() 应从PACKAGE2 使用& Method2() 应从PACKAGE1 使用

基本上我想在基于 Preference 的模块之间实现层次结构。有什么办法吗?

【问题讨论】:

虽然我可以尝试想出一些方法让它“工作”,但我有两个问题。 1)第二个输出应该是什么(“PACKAGE2_Method1”也是?)。 2)你能不能使用某种类调度,这样你就不会有这种巨大的歧义,这对于未来的人来说似乎是一场调试噩梦。 是的。第二个输出也是“PACKAGE2_Method1”。实际情况是,多个 Perl 模块中的方法将具有相同的名称。但是只能使用高优先级 perl 模块中的方法。例如,如果PACKAGE1 包含Method1(), Method2()PACKAGE2 仅包含Method1(),则Method1() 应取自PACKAGE2Method2() 应取自PACKAGE1。基本上我想在基于 Preference 的模块之间实现层次结构。 我同意 Joel 的观点,这听起来像是一场正在酝酿中的维护噩梦 - 当维护程序员(很可能是您自己的未来版本)看到对 Method1 的调用时,他将不知道在哪里寻找它的定义,很可能会在错误的地方寻找并尝试调试甚至没有运行的代码。当您说“模块之间的层次结构”时,给我的印象是您正在尝试重新发明面向对象的继承和多态性;如果是这样,请使用现有的、经过验证的 OO 技术,而不是糟糕地重新发明它们。 【参考方案1】:

在 Perl 中,use Module 等价于

BEGIN  require Module; Module->import; 

但是require 缓存了需要的模块列表。每个 Perl 进程只加载一次模块。所以只有第一个 use IMPORTS 做任何事情。由于您的IMPORTS 模块没有import 方法,因此当您再次use 时不会发生任何事情。

我不太确定您要完成什么。也许您的 IMPORTS 模块应该是一个实际的包,带有一个 import 方法,可以导出您想要的任何功能。这样,每个use IMPORTS 都会将函数导出到调用它的包中。

【讨论】:

我花了一些时间研究如何“正确”地做到这一点(实际上我仍然认为设计存在根本缺陷,我的意思是“工作”),但我一直在摸索为什么一个人能工作而另一个没有。关于缓存防止第二个问题,您当然是对的。我仍然无法弄清楚第一个是如何工作的? @JoelBerger,第一个有效,因为IMPORTS.pm 中没有package 语句。因此IMPORTS.pm 加载时的活动包仍然是Test1,并且use PACKAGE 语句将函数导入该包。但是因为已经加载了,所以第二个use IMPORTS没有执行任何代码。 现在我可以理解为什么do "INCLUDES.pm" 适用于这种情况了。因为,do 不检查模块是否已经加载。我说的对吗? 请检查已编辑的问题。我已经给出了我想要实现的场景 @InnovWelt,是的,do 每次都会执行IMPORTS.pm 中的代码,但use 每个进程只执行一次。【参考方案2】:

use MyPackage 等价于BEGIN require MyPackage; MyPackage->import 。从 Exporter 继承设置了一个 import 类方法,该方法执行“别名”功能。

问题是您包含模块没有正确重新导出模块。这很重要,因为这是将函数导入调用者命名空间的过程。虽然这并不难自己制作,但有一个方便的模块用于此目的Import::Into。

这是一个包含在单个文件中的示例,它应该很容易重新扩展为多个文件,唯一重要的区别在于包含模块。我做了一些其他表面上的改变,但这些更符合我的口味。

#!/usr/bin/env perl

use strict;
use warnings;

package PACKAGE1;

use parent 'Exporter';
our @EXPORT = qw(Method1);

sub Method1 
  print "PACKAGE1_Method1 \n";


package PACKAGE2;

use parent 'Exporter';
our @EXPORT = qw(Method1);

sub Method1 
  print "PACKAGE2_Method1 \n";


package Includes;

use Import::Into;

# uncomment in mulitple files
#use PACKAGE1 ();  # prevent import
#use PACKAGE2 ();  # ditto

sub import 
  my $class = shift;
  my $caller = caller;

  PACKAGE1->import::into( $caller );
  PACKAGE2->import::into( $caller );


package Test1;
Includes->import; # in seperate files replace with `use Includes;`

Method1();

package Test2;
Includes->import; # ditto

Method1();

一个真实的例子是模块utf8::all,它广泛使用这种机制将大量的Unicode内容加载到调用程序包中。

编辑

为了允许从 Includes 模块导入特定的东西,你可以让它继承自 Exporter 并制作它的 @EXPORT@EXPORT_OK 来做你的意思。否则,您可以继续使用Import::Into 并制作捆绑包之类的东西。

sub import 
  my $class  = shift;
  my $bundle = shift;

  my $caller = caller;

  if ($bundle eq 'Bundle1') 
    PACKAGE1->import::into( $caller );
    ... # other things in Bundle1
   elsif ($bundle eq 'Bundle2') 
    PACKAGE2->import::into( $caller );
    ... # other things in Bundle2
  

然后在你的测试模块中

use Includes 'Bundle1';

简而言之,制作您自己的import 方法并不难,而且Exporter 的每一点都很神奇。一旦您了解了符号表操作,您就不需要它或Import::Into,尽管它是一个稍微高级一点的主题。这是我在 Perl 时代早些时候问过的一个问题:Demystifying the Perl glob (*)

话虽如此,如果继承和多态的面向对象概念能够胜任这项工作,那么您可能也想研究这条路线。这是一个例子:

#!/usr/bin/env perl

use strict;
use warnings;

package PACKAGE1;

sub Method1 
  my $class = shift;
  print "PACKAGE1_Method1 \n";


sub Method2 
  my $class = shift;
  print "PACKAGE1_Method2 \n";


package PACKAGE2;

# if multiple files use this
#use parent 'PACKAGE1';
# rather than
our @ISA = 'PACKAGE1';

# any methods in PACKAGE2 will override those declared in PACKAGE1 

sub Method1 
  my $class = shift;
  print "PACKAGE2_Method1 \n";


package Test1;

# in seperate files need to use
#use PACKAGE2;

PACKAGE2->Method1();
PACKAGE2->Method2();

package Test2;

# ditto
#use PACKAGE1
#use PACKAGE2

PACKAGE2->Method1();
PACKAGE2->Method2();

# you can still use PACKAGE1 and get the originals
PACKAGE1->Method1();
PACKAGE1->Method2();

现在看到没有Includes 包,也没有符号被导入Test* 命名空间。 PACKAGE2 提供 Method2 是因为它继承自 PACKAGE1 并且它不会用自己的方法声明覆盖方法声明。

【讨论】:

嗨乔尔,谢谢。有用。请检查已编辑的问题。我已经给出了我想要实现的场景 @InnovWelt,你了解继承和多态的概念吗?如果不阅读有关它们的信息。如果是这样,但这仍然无法将您带到您想要的地方,请查看我的编辑。 我确实知道一些继承的概念,包括函数覆盖、多态。我的意图是,在Tests.pm 文件中,函数调用应该是Method1(),它不应该依赖于任何特定的包(如PACKAGE2->Method1())。因为,情况是,测试将是相同的(包括函数调用)。只有特定功能的实现应该根据偏好而有所不同

以上是关于Perl:同一个文件中的两个包不能导入同一个包?的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis 项目的 jar 包导入与源码导入

关于python包,模块,.pyc文件和文件导入理解

导入jar包,main函数

为啥在java中不能导入awt包

怎么引入servlet中的两个包

是否有一个 Perl 统计包不能让我一次加载整个数据集?