4

这个例子工作正常:

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

warn File::Slurp::read_file('/root/test.txt'); # return 'test'

这个也是:

use File::Slurp qw(read_file);
local *read_file = sub {
    return 'test';
};

warn read_file('/root/test.txt');   # return 'test'

但是如果我在 typeglob 中使用函数的全名,它就不起作用并尝试读取文件:

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

warn read_file('/root/test.txt');

谁能解释为什么我不能通过完整的命名空间重新定义子例程File::Slurp::read_file,并通过短名称使用?

在对象方法的情况下,它工作正常:

use LWP::UserAgent;
local *LWP::UserAgent::get = sub {
    return HTTP::Response->new( undef, undef, undef, 'Hello world' );    
};

my $ua = LWP::UserAgent->new;
warn $ua->get()->content;
4

2 回答 2

6

您的问题是由导出的工作方式引起的。以及 Perl 如何将名称分配给值。在 Perl 中,每个名称都是一个值的链接,因此sub read_line { ... }创建一个匿名子例程引用并将其分配给该名称&read_line

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

在您的第一个示例中,您正在覆盖File::Slurp::read_file然后调用File::Slurp::read_file,以便获得您的File::Slurp::read_file.

use File::Slurp qw(read_file);
local *read_file = sub {
    return 'test';
};

在您的第二个示例中,您将覆盖您的导入版本,read_file然后调用它,以便您获得您的版本read_file

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

在您的第三个示例中,发生以下情况:

use File::Slurp;在编译时执行 a *read_file = \&File::Slurp::read_file,这read_file表明File::Slurp::read_file. 然后,您的代码会分配*File::Slurp::read_file一​​个新的子引用,但这不会改变,它仍然指向最初指向read_file的子引用。File::Slurp::read_file然后你调用read_filewhich 指向原始导入的版本File::Slurp::read_file

在您的第四个示例中,Perl 的方法解析系统意味着您正在调用LWP::UserAgent::get,因此这等效于您的第一个示例。

于 2018-07-31T10:14:52.913 回答
1

当一个子被导出时,它的引用被写入调用者的符号表。显然,在您重新定义模块中的子模块后,调用者中的非限定名称仍然指的是“旧”名称,即已导出,而不是重新定义的名称。

一个明确的解决方法是在调用包中显式地为(非限定)名称设置别名

*func = *Module::func = sub { ... };

然后将其包装在一个子例程中,从中可以处理所有想要的名称空间

sub redefine_sub {
    my ($fqn, $code) = @_;

    no warnings 'redefine';  # these pragmas are lexical, and
    no strict 'refs';        # so stay scoped to this sub only

    *{ $fqn } = $code;

    # Redefine in caller
    my ($name) = $fqn =~ /.*::(.*)/;
    my $to_caller = caller() . '::' . $name;
    *{ $to_caller } = $code;
}

在调用者

use Module qw(func);

redefine_sub('Module::func', sub { ... });

一些原始尝试,保留在这里,因为它们可能看起来合理但不起作用

有人可能会认为(或者,我确实认为)切换定义的顺序应该有效

use warnings;
use strict;
use feature 'say';

BEGIN {                      # must come first, in BEGIN block
    no warnings 'redefine';
    *Cwd::cwd = sub { return 'impostor' }; 
};

use Cwd;

say "cwd(): ", cwd();

这确实打印impostor。但是,比要求特定的定义顺序并且不再允许更糟糕的是local,这也不适用于File::Slurp. 我看不出这些模块的来源有什么不同。

它确实适用于同一文件中定义的简单明了的模块

use warnings;
use strict;
use feature 'say';

BEGIN {
    package TestRedef;    
    use Exporter qw(import);    
    our @EXPORT = qw(hi);

    sub hi { return "\thi from " . __PACKAGE__; }

    $INC{'TestRedef.pm'} = 1;
};

###  in main::

BEGIN {
    no warnings 'redefine';
    *TestRedef::hi = sub { return 'impostor in ' . __PACKAGE__ };
};

use TestRedef;

say hi();

但如果这个包在一个单独的文件中给出,它又不起作用。

于 2018-07-31T10:15:00.263 回答