为什么 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
您正在体验 packages 和 modules 之间的区别。一个包就是一个命名空间。一个模块就是一个文件。很容易混淆它们,因为按照惯例,Foo::Bar::Baz
命名空间的定义将在文件 Foo/Bar/Baz.pm
.
中
当你写作时:
use Foo::Bar::Baz;
Perl 将其解释为两条指令:
- 加载文件
Foo/Bar/Baz.pm
(如果尚未加载)。
- 在
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';
parent
是 base
的更现代版本,并且有一个 -norequire
选项可以跳过步骤 #1。
既不使用基数也不使用 parent
所有这些模块都在为您设置 @ISA
变量。你可以自己做。
{
package C;
our @ISA = 'A';
}
我试图将 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
您正在体验 packages 和 modules 之间的区别。一个包就是一个命名空间。一个模块就是一个文件。很容易混淆它们,因为按照惯例,Foo::Bar::Baz
命名空间的定义将在文件 Foo/Bar/Baz.pm
.
当你写作时:
use Foo::Bar::Baz;
Perl 将其解释为两条指令:
- 加载文件
Foo/Bar/Baz.pm
(如果尚未加载)。 - 在
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';
parent
是 base
的更现代版本,并且有一个 -norequire
选项可以跳过步骤 #1。
既不使用基数也不使用 parent
所有这些模块都在为您设置 @ISA
变量。你可以自己做。
{
package C;
our @ISA = 'A';
}