@ISA vs use base vs use parent

时间:2018-03-14 13:40:11

标签: perl

跑过这个小小的金块......我想我已经弄清楚了,但看看是否有人可以放下更多的光。

我的PERL5LIB环境变量中有一个用于记录的模块。它位于e:/Scripts/Log/Logger.pm。以下是摘录:

package Log::Logger;
...

package Log::NullLogger;
use base 'Log::Logger';
...

package Log::FileLogger;
use base 'Log::Logger';
...

package Log::DateFileLogger;
use base 'Log::FileLogger';
...

package LogTester;
use Data::Dump 'dump';
test() unless caller();
sub test {
    warn dump \%INC;
    ...
}

我想对此模块进行一些更改,因此我将其复制到e:/Test/Log/Log/Logger.pm并开始进行更改和测试...我添加了一个新类并更改了Log::Logger包中的继承例程。 ..

package BasicFormatter;
...

但我的测试很奇怪。一些新代码正在使用,有些则没有。 %INC的转储有:

"Log/Logger.pm" => "e:/Scripts/Log/Logger.pm"

... 旧模块!据我所知,use base 'Log::Logger'Log/Logger.pm出去并重新加载PERL5LIB,只替换其中的那些包(即package BasicFormatter来自新模块,但是所有其他包都来自旧模块。)

我读到了use parent,当我用use base替换use parent时,use parent 'Log::FileLogger'上的编译错误表面上是因为没有Log/FileLogger.pm模块。 use base从未抱怨过这一点 - 我认为use parent存在弱点。

我添加了

use lib '..';

到模块的顶部,一切都与use base一起工作,但这不是我喜欢的修复,因为当我发布"时它不会出现在最后一个模块中。它回到e:/Scripts/Log/Logger.pm。使用use lib '..'%INC现在显示

"Log/Logger.pm" => "../Log/Logger.pm"

这是有道理的。

我的下一步是删除use lib '..'并将所有use base...替换为our @ISA=(...)。当我这样做时,代码开始工作,%INC根本没有显示Log/Logger.pm的条目。

注意,当我在e:/Test/Log/TestLogger.pl中使用

设置测试脚本时
use lib '.';

在其中,它会提起e:/Test/Log/Log/Logger.pm罚款,而use base来电则不会重新要求该套餐。

所以,我的结论就是这样......当模块作为modulino运行时,模块不会被加载&#34;就Perl而言。因此,use base对该模块的第一次尝试会执行require <module>,然后从@INC路径加载它,这可能会替换正在执行的modulino中的包。使用@ISA会绕过对require的调用,因此模块不会从@INC加载。

其他人得到了更多关于此的信息......正在测试一个模块作为modulino只是一个坏主意?

3 个答案:

答案 0 :(得分:5)

我认为澄清一些事情是有用的:

@ISA

用于定义包的父类。它不会影响包的加载方式。您可以直接操作它,也可以使用parent或略长的base pragma。直接操纵@ISA和这两个pragma之间的区别在于pragma执行的更多:use base qw/Foo Bar/;use parent qw/Foo Bar/;在效果上大致相似

BEGIN {
    require Foo;
    require Bar;
    push @ISA, qw(Foo Bar);
}

意味着他们不仅会操纵@ISA,还会使用require加载包。有一个例外,即use parent -norequire, qw/Foo Bar/; - 如果你似乎在展示所有的软件包都驻留在同一个文件中,这可能很有用。但是,两个pragma都不会影响require的操作方式,包括它的外观,因为您需要操作@INC

@INC

是Perl的userequiredo查找文件的地方(后者有一些例外)。有几种方法可以操纵@INC

Perl按顺序查看@INC。因此,在您的情况下,如果Perl加载了错误版本的Log::Logger,这意味着Perl会在@INC中找到它,而不是您实际想要加载的版本。我建议检查@INC并确保只包含您实际想要加载模块的路径,或者放置&#34;开发的路径。 Log::Logger中较早的@INC版本。例如,当我开发模块时,我通常在命令行上指定开发版本的目录,例如: perl -Ilib ...

请注意,Perl 5.26发生了变化:the current directory . was removed as a default entry in @INC。 (Perl 5.24.1 also introduced一些相关的变化。)

%INC

基本上只是跟踪已经加载了哪些模块,因此requireuse不会加载相同的模块两次。虽然可以通过手动将其条目添加到require来欺骗%INC而不查找文件,但通常有更好的解决方案。

答案 1 :(得分:5)

parent要求您明确说明您是希望它实际加载父模块还是仅设置继承。这是一种力量,而不是弱点; base总是尝试加载模块的弱点,导致您遇到的问题。

执行:

use parent -norequire => 'Log::Logger';
...
use parent -norequire => 'Log::FileLogger';

也就是说,如果您始终使用baseuse加载模块,则问题require不会发生;如果您只是运行一个模块(例如perl Foo/Bar.pm)或以其他方式加载它(use Test::Log::Logger而不是use lib 'Test'; use Log::Logger ??)并且您的代码也尝试use它,那么您总是冒险找到了多个版本。

答案 2 :(得分:2)

我建议你将每个模块放在一个单独的文件中。要内联模块以便您可以使用use Module,您可以通过替换

获得最佳结果
use Log::Logger;

BEGIN {
   package Log::Logger;
   ...

   $INC{"Log/Logger.pm"} = 1;
}

BEGIN { import Log::Logger; }

然后,use Log::Logger;(包括use base 'Log::Logger';use parent 'Log::Logger';)的未来使用将正常运行,包括导出。

在这种特殊情况下,@ ysth的解决方案就足够了。无论如何,我建议远离base.pm。