2010-01-19 99 views
9

前段时间,我问This question关于重写perl函数的功能。如何覆盖Perl函数,启用多个覆盖?

如何以允许多重覆盖的方式执行此操作?以下代码产生无限递归。

这样做的正确方法是什么?如果我重新定义一个函数,我不希望踩到别人的重新定义。

package first; 

my $orig_system1; 
sub mysystem { 
    my @args = @_; 
    print("in first mysystem\n"); 
    return &{$orig_system1}(@args); 
} 

BEGIN { 

    if (defined(my $orig = \&CORE::GLOBAL::system)) { 
    $orig_system1 = $orig; 
    *CORE::GLOBAL::system = \&first::mysystem; 
    printf("first defined\n"); 
    } else { 
    printf("no orig for first\n"); 
    } 
} 

package main; 

system("echo hello world"); 

回答

20

这样做的正确方法就是不要这样做。找一些其他的方式来完成你在做什么。这种技术具有全局变量的所有问题,平方。除非你完全正确地重写了函数,否则你可能会破解你从来不知道存在的各种代码。虽然你可能有礼貌不吹嘘现有的重写,但其他人可能不会。

覆盖system特别敏感,因为它没有适当的原型。这是因为它做的事情在原型系统中不可表达。这意味着你的覆盖不能做一些system可以做的事情。即...

system {$program} @args; 

这是调用system的一种有效方式,但你需要阅读exec文档做到这一点。你可能会认为“哦,我现在不会那么做”,但是如果你使用的任何模块都使用了它,或者它使用了它的任何模块,那么你就没有运气了。

也就是说,与礼貌地重写任何其他函数没有什么不同。你必须捕捉现有的功能,并确保你在新的功能中调用它。无论你在之前还是之后做到这一点,取决于你。

代码中的问题是,检查函数是否被定义的正确方法是defined &function。采用代码ref,即使是未定义的函数,也会始终返回真实的代码ref。我不知道为什么,也许它像\undef将返回一个标量ref。为什么调用这个代码引起mysystem()无限递归是任何人的猜测。

有一个额外的复杂性,你不能参考核心功能。 \&CORE::system不符合你的意思。你也不能用象征性的参照来解决它。所以,如果你想打电话CORE::system或取决于定义的现有覆盖,你不能只分配一个或另一个代码ref。你必须分裂你的逻辑。

这是一种方法。

package first; 

use strict; 
use warnings; 

sub override_system { 
    my $after = shift; 

    my $code; 
    if(defined &CORE::GLOBAL::system) { 
     my $original = \&CORE::GLOBAL::system; 

     $code = sub { 
      my $exit = $original->(@_); 
      return $after->($exit, @_); 
     }; 
    } 
    else { 
     $code = sub { 
      my $exit = CORE::system(@_); 
      return $after->($exit, @_); 
     }; 
    } 

    no warnings 'redefine'; 
    *CORE::GLOBAL::system = $code; 
} 

sub mysystem { 
    my($exit, @args) = @_; 
    print("in first mysystem, got $exit and @args\n"); 
} 

BEGIN { override_system(\&mysystem) } 

package main; 

system("echo hello world"); 

请注意,我已将mysystem()更改为仅在真实系统之后运行的挂钩。它获取所有参数和退出代码,它可以更改退出代码,但不会更改实际执行的操作。在钩子之前/之后添加是您可以做的唯一事情,如果您想要兑现现有的覆盖。无论如何,它更安全一些。压倒一切的系统现在是一个子程序,以防止BEGIN变得过于混乱。

您应该可以根据自己的需求进行修改。