Perl:同一个文件中的两个包不能导入同一个包?
Posted
技术标签:
【中文标题】Perl:同一个文件中的两个包不能导入同一个包?【英文标题】:Perl : Two packages in same file cannot import same package? 【发布时间】:2013-01-17 12:14:14 【问题描述】:这是一个有趣的 Perl 行为。 (至少对我来说:))
我有两个包 PACKAGE1
和 PACKAGE2
导出具有相同名称的函数 Method1()
。
由于会有很多包导出相同的功能,use
-ing Perl 文件中的所有内容都将是乏味的。所以,我创建了一个通用包含文件INCLUDES.pm
,其中将包含这些use
s。
包括.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()
应取自PACKAGE2
和Method2()
应取自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:同一个文件中的两个包不能导入同一个包?的主要内容,如果未能解决你的问题,请参考以下文章