为什么 perl 在已经加载时尝试重新加载模块?

Why perl tries to reload module when it is already loaded?

我试图将 Carton 安装到我的项目的本地目录中,但出现错误:

--> Working on Test::Deep
Fetching...
...
t/isa.t ..................... Can't locate Mojo/Base.pm in @INC (you may need to install the Mojo::Base module) (@INC contains: CODE(0x557913d006d0) t/lib /home/kes/.cpanm/work/1626520042.29670/Test-Deep-1.130/blib/lib /home/kes/.cpanm/work/1626520042.29670/Test-Deep-1.130/blib/arch /home/kes/work/projects/tucha/monkeyman/local/lib/perl5/x86_64-linux /home/kes/work/projects/tucha/monkeyman/local/lib/perl5 /home/kes/work/projects/tucha/monkeyman/lib /home/kes/perl5/perlbrew/perls/perl-5.35.1/lib/site_perl/5.35.1/x86_64-linux /home/kes/perl5/perlbrew/perls/perl-5.35.1/lib/site_perl/5.35.1 /home/kes/perl5/perlbrew/perls/perl-5.35.1/lib/5.35.1/x86_64-linux /home/kes/perl5/perlbrew/perls/perl-5.35.1/lib/5.35.1 CODE(0x557913d00670) .) at /home/kes/work/projects/tucha/monkeyman/lib/A.pm line 2.
BEGIN failed--compilation aborted at /home/kes/work/projects/tucha/monkeyman/lib/A.pm line 2.
Compilation failed in require at /home/kes/perl5/perlbrew/perls/perl-5.35.1/lib/5.35.1/base.pm line 138.
    ...propagated at /home/kes/perl5/perlbrew/perls/perl-5.35.1/lib/5.35.1/base.pm line 160.
BEGIN failed--compilation aborted at t/isa.t line 133.

发生此错误是因为我的应用程序有 A 模块,但 t/isa.t 已经定义了它自己的 package A,它已经加载到内存中。

# Test::Deep:t/isa.t:120
package A;

use Test::Deep;
@A::ISA = qw( Test::Deep );

{
  ::ok(A->isa("Test::Deep"), "U::isa says yes");
  ::ok(! A->isa("Test"), "U::isa says yes");
}


{
  package C;
  use base 'A';    # <<<< this cause error
}

但是为什么 use base 'A' 尝试从磁盘重新加载包 A 而 package A 已经加载到内存中?

perl -v 5.35.1

您正在体验 packagesmodules 之间的区别。一个包就是一个命名空间。一个模块就是一个文件。很容易混淆它们,因为按照惯例,Foo::Bar::Baz 命名空间的定义将在文件 Foo/Bar/Baz.pm.

当你写作时:

use Foo::Bar::Baz;

Perl 将其解释为两条指令:

  1. 加载文件 Foo/Bar/Baz.pm(如果尚未加载)。
  2. Foo::Bar::Baz 命名空间上调用方法 import

(和 use base 'Foo::Bar::Baz' 类似,除了它不是 #2,它通过继承做了一些时髦的事情。)

所以在您的情况下,当您执行 use base 'A' 时,Perl 将执行 #1,除非文件 A.pm 已经加载。是的,您已经在 A 命名空间中定义了一些内容,但这并不重要。

几种不同的解决方案。

欺骗 Perl 认为 A.pm 已经加载

use base 'A' 之前的某处添加此行将诱使 Perl 认为 A.pm 已经加载。

BEGIN { $INC{'A.pm'} = __FILE__ };

使用parent

这样做而不是 use base 'A':

use parent '-norequire', 'A';

parentbase 的更现代版本,并且有一个 -norequire 选项可以跳过步骤 #1。

既不使用基数也不使用 parent

所有这些模块都在为您设置 @ISA 变量。你可以自己做。

{
  package C;
  our @ISA = 'A';
}