【问题标题】:Redefine perl function using typeglob doesn't work as expected使用 typeglob 重新定义 perl 函数不能按预期工作
【发布时间】:2018-07-31 09:30:24
【问题描述】:

这个例子运行良好:

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;

【问题讨论】:

    标签: perl typeglob


    【解决方案1】:

    您的问题是由导出的工作方式引起的。以及 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,因此这与您的第一个示例等效。

    【讨论】:

    • 很好的解释,我可能只是补充说“read_file 的导入版本”(OP 称之为 “短名称”)是 main::read_file,即什么都没有它真的很特别 - 它也只是一个常规符号表条目,默认为package main
    • 我考虑过这一点,但决定在示例中没有任何内容可以显示它们是在 main:: 包中还是在另一个包中,所以我写它时没有显示符号被导入的包
    • 是的。我只是不想给人留下关于导入或“短名称”(默认/当前包中的东西)特别“神奇”的印象,因此我的评论。
    • 还不错:-)
    【解决方案2】:

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

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

    *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();
    

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

    【讨论】:

    • 如果您将Cwd 替换为File::Slurp,这对您有用吗?奇怪的是,我无法让它与 File::Slurp.. 一起使用
    • @HåkonHægland 你是对的,它没有——谢谢。这里太晚了,所以我现在要删除它。
    • @HåkonHægland 编辑以解决这个问题,谢谢。我看不出这些模块中有什么不同之处,而且我发现了一些我没有得到的更微妙的行为。所以我看到的唯一解决方法是在所有需要的命名空间中显式设置名称。
    • @PaulSerikov 已编辑,并添加了一种可能合理的方式来做你想做的事
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多