首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用typeglob重新定义perl函数不能按预期工作

使用typeglob重新定义perl函数不能按预期工作
EN

Stack Overflow用户
提问于 2018-07-31 17:30:25
回答 2查看 238关注 0票数 4

这个示例运行得很好:

代码语言:javascript
复制
use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

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

这个也是:

代码语言:javascript
复制
use File::Slurp qw(read_file);
local *read_file = sub {
    return 'test';
};

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

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

代码语言:javascript
复制
use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

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

有人能解释为什么我不能用全名、File::Slurp::read_file和短名重定义子例程吗?

在对象方法的情况下,它工作得很好:

代码语言:javascript
复制
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;
EN

回答 2

Stack Overflow用户

发布于 2018-07-31 18:14:53

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

代码语言:javascript
复制
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版本。

代码语言:javascript
复制
use File::Slurp qw(read_file);
local *read_file = sub {
    return 'test';
};

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

代码语言:javascript
复制
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最初指向的子引用。然后调用指向File::Slurp::read_file的原始导入版本的read_file

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

票数 6
EN

Stack Overflow用户

发布于 2018-07-31 18:15:00

导出sub时,其引用将写入调用方的符号表。显然,在模块中重定义子模块后,调用方中的非限定名称仍然引用已导出的“旧”名称,而不是重新定义的名称。

一种明确的解决方法是在调用包中显式地给(非限定)名称加上别名

代码语言:javascript
复制
*func = *Module::func = sub { ... };

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

代码语言:javascript
复制
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;
}

在调用方中

代码语言:javascript
复制
use Module qw(func);

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

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

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

代码语言:javascript
复制
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。我看不出这些模块的源代码有什么不同。

它确实适用于在同一文件中定义的头脑简单的普通模块

代码语言:javascript
复制
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();

但是,如果这个包是在一个单独的文件中提供的,它同样不起作用。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51610058

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档