使用typeglob重新定义perl函数无法正常工作

时间:2018-07-31 09:30:25

标签: perl typeglob

此示例可以正常工作:

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;

2 个答案:

答案 0 :(得分: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;在编译时执行*read_file = \&File::Slurp::read_file,这使read_file指向File::Slurp::read_file的现有版本。然后,您的代码为*File::Slurp::read_file分配了一个新的子引用,但这并没有改变read_file,它仍然指向File::Slurp::read_file最初指向的子引用。然后,您调用read_file,该指向原始导入版本的File::Slurp::read_file

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

答案 1 :(得分:1)

导出子项时,其引用将被写入调用者的符号表。显然,在模块中重新定义了子项之后,调用者中的不合格名称仍然引用已导出的“旧”名称,而不是重新定义的名称。

一个明确的解决方法是在调用包中显式别名(不合格)

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

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

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

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

    *{ $fqn } = $rc;

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

在呼叫者中

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更糟糕的是,这对<{1}} 也无效。我看不出这些模块的来源有什么不同。

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

File::Slurp

,但是如果此软件包在单独的文件中提供,则再次无效。