@ISA vs use base vs use parent

msw*_*erg 2 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;
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

package BasicFormatter;
...
Run Code Online (Sandbox Code Playgroud)

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

"Log/Logger.pm" => "e:/Scripts/Log/Logger.pm"
Run Code Online (Sandbox Code Playgroud)

...... 旧模块!从我可以告诉,use base 'Log::Logger'走了出去,并重新加载Log/Logger.pmPERL5LIB,替换只有在它的包装(即package BasicFormatter由新模块来了,但其他所有的包都是从旧的模块).

我读到了use parent,当我替换我时use base,use parentuse parent 'Log::FileLogger'表面上得到了编译错误,因为没有Log/FileLogger.pm模块. use base从来没有抱怨过这个 - use parent在我看来是一个弱点.

我补充道

use lib '..';
Run Code Online (Sandbox Code Playgroud)

到模块的顶部,一切都有效,use base但这不是我喜欢的修复,因为当我"发布"回到它时,它不会出现在最终模块中e:/Scripts/Log/Logger.pm.随着use lib '..',%INC现在显示

"Log/Logger.pm" => "../Log/Logger.pm"
Run Code Online (Sandbox Code Playgroud)

这是有道理的.

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

注意,当我在中设置测试脚本e:/Test/Log/TestLogger.pl

use lib '.';
Run Code Online (Sandbox Code Playgroud)

在它,它收起来e:/Test/Log/Log/Logger.pm很好,use base电话不需要包.

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

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

yst*_*sth 6

parent要求您明确声明是要它实际加载父模块还是只是设置继承。这是优势,而不是劣势;base总是尝试加载模块的缺点,导致出现问题。

做:

use parent -norequire => 'Log::Logger';
...
use parent -norequire => 'Log::FileLogger';
Run Code Online (Sandbox Code Playgroud)

等等

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


hau*_*kex 5

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

@ISA

用于定义包的父类.它不会影响包的加载方式.您可以直接操作它,也可以使用parent稍微较旧的basepragma.@ISA直接操作和这两个pragma 之间的区别在于pragma做了一点:use base qw/Foo Bar/;并且use parent qw/Foo Bar/;两者在效果上大致相似

BEGIN {
    require Foo;
    require Bar;
    push @ISA, qw(Foo Bar);
}
Run Code Online (Sandbox Code Playgroud)

意思是他们不仅操纵@ISA,他们也加载包require.有一个例外,use parent -norequire, qw/Foo Bar/;这可能是有用的,如果您似乎正在显示,所有的包都驻留在同一个文件中.但是,两个pragma都不会影响require您需要操作的操作方式,包括它的外观@INC.

@INC

是Perl的地方use,requiredo查找文件(后者也有少数例外).有几种方法可以操纵@INC:

Perl @INC按顺序查看.因此,在您的情况下,如果Perl加载了错误的版本Log::Logger,这意味着Perl会在@INC您实际想要加载的版本之前找到它.我会建议检查@INC并确保你只包括您实际要加载的模块,或将路径的"发展"的版本,这些路径Log::Logger前面@INC.例如,当我开发模块时,我通常在命令行上指定开发版本的目录,例如perl -Ilib ....

请注意,从Perl 5.26开始有一个更改:当前目录.被删除为默认条目@INC.(Perl 5.24.1 还介绍了一些相关的更改.)

%INC

基本上只是跟踪哪些模块已经被加载,从而使requireuse不加载同一模块的两倍.虽然可以require通过%INC手动添加其条目来欺骗不查找文件,但通常有比此更好的解决方案.