【问题标题】:Why does this Perl produce "Not a CODE reference?"为什么这个 Perl 会产生“Not a CODE reference”?
【发布时间】:2011-01-12 22:08:21
【问题描述】:

我需要在运行时从 Perl 符号表中删除一个方法。我尝试使用undef &Square::area 来执行此操作,它确实删除了该函数但留下了一些痕迹。具体来说,当调用 $square->area() 时,Perl 抱怨它是“不是代码引用”而不是“未定义的子例程 &Square::area called”,这是我所期望的。

你可能会问,“为什么重要?你删除了这个函数,你为什么要调用它?”答案是我不叫它,Perl 是。 Square 继承自 Rectangle,我希望继承链将 $square->area 传递给 &Rectangle::area,但不是跳过该方法不存在的 Square 然后落入 Rectangle 的 area(),方法调用以“不是代码参考。”

奇怪的是,这似乎只发生在 &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,我将不胜感激。

【问题讨论】:

  • 你从哪里得知undef &Square::area 会删除一个子程序?不是攻击,只是好奇……
  • 来自perldoc -f undef。他们给出的例子是undef &mysub;,但它也适用于限定名称。
  • 替代“你可能会问”的问题。你为什么首先定义 &Square::area ?为什么不 [重新] 定义 &Square::area 以委托给它的超类?
  • @JB:这是用于测试设备,而不是实时代码。

标签: perl subroutine symbol-tables typeglob


【解决方案1】:

自己操作符号表引用肯定会给你带来麻烦,因为有很多小事很难做到。幸运的是,有一个模块可以为您完成所有繁重的工作,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 = { '你好' => *::你好, 'foo' => *::foo, };

如您所见,'howdy' 插槽仍然存在 - 未定义 &howdy 实际上并没有做 任何事情。您需要明确删除 glob 插槽 *howdy

【讨论】:

  • 感谢指向 Package::Stash 的指针。我上次看时不知何故错过了 remove_package_symbol。这是我需要的,而不是 remove_package_glob,它也会破坏 @area 和 $area 等。但是虽然这是有用的信息,但它并没有回答问题,而是说“这是一个很难的问题,不要麻烦问一下。”
  • @KingPong:重新加载您的页面;解释只是花了一点时间来写。 :)
  • @Esther:如果你undef &foo,你会看到它仍然在符号表中,就像howdy()一样,即使你取消了它。不同之处在于 Perl 知道 foo() 消失了,大概是因为您使用“sub foo {...}”声明它,根据perldoc perlmod,它等同于BEGIN { *foo = sub {... } }
【解决方案2】:

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

当您删除 CODE 符号时,typeglob 的其余部分仍然存在,因此当您尝试执行 howdy 时,它将指向非CODE 的 typeglob。

【讨论】:

    猜你喜欢
    • 2012-08-08
    • 1970-01-01
    • 1970-01-01
    • 2013-03-10
    • 1970-01-01
    • 1970-01-01
    • 2022-09-23
    • 2015-09-19
    • 1970-01-01
    相关资源
    最近更新 更多