为什么这个Perl产生“不是CODE参考?”

时间:2011-01-12 22:08:21

标签: perl subroutine symbol-tables typeglob

我需要在运行时从Perl符号表中删除一个方法。我尝试使用undef &Square::area执行此操作,这会删除该功能但会留下一些痕迹。具体来说,当调用$square->area()时,Perl会抱怨它是“不是CODE引用”而不是“Undefined subroutine& Square :: area called”,这就是我所期望的。

您可能会问,“为什么这很重要?您删除了该功能,为什么要调用它?”答案是,我不是在呼唤它,Perl是。 Square继承自Rectangle,我希望继承链将$square->area传递给&Rectangle::area,但不是跳过方法不存在的Square,而是直接转到Rectangle的area(),方法使用“Not a CODE reference”调用die。

奇怪的是,这似乎只发生在& Square :: area由typeglob赋值(例如*area = sub {...})定义时。如果使用标准sub area {}方法定义函数,则代码按预期工作。

同样有趣的是,取消定义整个glob按预期工作。只是不要取消定义子程序本身。

这是一个简短的例子,说明症状,并与正确的行为形成对比:

#!/usr/bin/env perl
use strict;
use warnings;

# This generates "Not a CODE reference". Why?
sub howdy; *howdy = sub { "Howdy!\n" };
undef &howdy;
eval { howdy };
print $@;

# Undefined subroutine &main::hi called (as expected)
sub hi { "Hi!\n" }
undef &hi;
eval { hi };
print $@;

# Undefined subroutine &main::hello called (as expected)
sub hello; *hello = sub { "Hello!\n" };
undef *hello;
eval { hello };
print $@;

更新:我已经使用Package :: Stash解决了这个问题(感谢@Ether),但我仍然对为什么它首先发生的事情感到困惑。 perldoc perlmod说:

  

package main;

     

sub Some_package::foo { ... } # &foo defined in Some_package

     

这只是编译时typeglob赋值的简写:

     

BEGIN { *Some_package::foo = sub { ... } }

但似乎它不是只是的简写,因为这两者在取消定义函数后会导致不同的行为。如果有人能告诉我这是否是(1)不正确的文档,(2)perl中的错误,或(3)PEBCAK的情况,我将不胜感激。

2 个答案:

答案 0 :(得分:7)

自己操纵符号表引用必然会让你陷入困境,因为有许多令人费解的事情难以理解。幸运的是,有一个模块可以为您完成所有繁重工作,Package::Stash - 所以只需根据需要调用其方法add_package_symbolremove_package_symbol

您可能希望签出的另一个好的方法安装程序是Sub::Install - 如果您想生成许多类似的功能,那就太好了。

至于为什么你的方法不正确,让我们在删除代码参考后看一下符号表:

sub foo { "foo!\n"}
sub howdy; *howdy = sub { "Howdy!\n" };

undef &howdy;
eval { howdy };
print $@;

use Data::Dumper;
no strict 'refs';
print Dumper(\%{"main::"});

打印(删节):

    $VAR1 = {
              'howdy' => *::howdy,
              'foo' => *::foo,
    };

正如你所看到的,'howdy'插槽仍然存在 - 取消定义&howdy实际上并没有任何。您需要显式删除glob插槽*howdy

答案 1 :(得分:2)

它发生的原因恰恰是因为你分配了一个typeglob。

当您删除CODE符号时,其余的typeglob仍然会延迟,所以当您尝试执行时它会指向非CODE的typeglob。