Perl方法属性如何工作?

时间:2009-06-12 14:51:39

标签: perl attributes

一个鲜为人知的内置Perl功能是属性。然而,官方documentation在介绍新概念方面做得相当糟糕。与此同时,像Catalyst这样的框架广泛使用属性,这似乎使许多事情变得更容易。由于在不知道其含义的情况下使用了一些东西,我想知道细节。语法方面它们看起来像Python的装饰器,但文档暗示了更简单的东西。

你能解释一下(如果可能的话,还有现实世界的例子)哪些属性有利于什么以及门后会发生什么?

3 个答案:

答案 0 :(得分:37)

你是对的,文档在这方面不是很清楚,特别是因为属性并不那么复杂。如果定义子例程属性,请执行以下操作:

sub some_method :Foo { }

Perl将在编译程序时 (这很重要)在当前包或其任何父类中查找magic sub MODIFY_CODE_ATTRIBUTES。这将使用当前包的名称,对子例程的引用以及为此子例程定义的属性列表进行调用。如果此处理程序不存在,则编译将失败。

您在此处理程序中所做的事完全取决于您。恩,那就对了。没有任何隐藏的魔法。如果要发出错误信号,则返回有问题的属性的名称将导致编译失败并显示“无效属性”消息。

还有一个名为FETCH_CODE_ATTRIBUTES的处理程序,只要有人说

就会被调用
use attributes;
my @attrs = attributes::get(\&some_method);

这个处理程序传递了包名和子例程引用,并且应该返回子例程属性的列表(尽管你真正做的事情再取决于你)。

这是一个使用任意属性对方法进行简单“标记”的示例,您可以稍后查询:

package MyClass;
use Scalar::Util qw( refaddr );

my %attrs; # package variable to store attribute lists by coderef address

sub MODIFY_CODE_ATTRIBUTES {
    my ($package, $subref, @attrs) = @_;
    $attrs{ refaddr $subref } = \@attrs;
    return;
}

sub FETCH_CODE_ATTRIBUTES {
    my ($package, $subref) = @_;
    my $attrs = $attrs{ refaddr $subref };
    return @$attrs;
}

1;

现在,在MyClass及其所有子类中,您可以使用任意属性,并使用attributes::get()查询它们:

package SomeClass;
use base 'MyClass';
use attributes;

# set attributes
sub hello :Foo :Bar { }

# query attributes
print "hello() in SomeClass has attributes: ",
      join ', ', attributes::get(SomeClass->can('hello'));

1;
__END__
hello() in SomeClass has attributes: Foo, Bar

总之,属性不会做太多,另一方面使它们变得非常灵活:你可以将它们用作真正的“属性”(如本例所示),实现类似装饰器的东西(参见Sinan's answer },或为了你自己的狡猾目的。

答案 1 :(得分:11)

答案 2 :(得分:5)

如果您不知道如何使用它们,属性就是其中之一,您不应该为它们烦恼。我曾经创建了一个database_method属性,用于向系统指示在进入此方法之前将请求记录集,并且该方法知道它的主要输入将来自它对应的存储过程。

我使用属性用这些数据包装实际的指定操作。因此,一个真正看似有用的想法是使用间接包装方法,但是如果不重写它就更难使调用者工作。最后,作为一个“仅限专家”的功能,它太明显了,如果你在perl-also商店里写Perl,你需要支持来追踪神秘的内脏 - 你要避免的事情。


人们可能想投票给我,但我从思南引用的文章中得到了:

  

<强>注意事项

     

虽然这是一种强大的技术,但它并不完美。 代码将无法正确包装匿名子例程,而它不一定会将调用上下文传播到包装函数。此外,使用此技术将显着增加程序在运行时必须执行的子例程调度的数量。根据程序的复杂程度,这可能会显着增加调用堆栈的大小。如果炫目速度是一个主要的设计目标,这个策略可能不适合你。

除非您愿意覆盖caller,否则这些重要的缺点。我不太关心“致盲速度”,我不愿意尝试覆盖caller来绕过任何将自己注册为“DO_NOT_REPORT”的子程序 - 但我有一些编码我还没有被打败的愚蠢。

即便是这篇文章也承认这个功能有多么缺乏记录,并且包含了这个警告。告诉我什么时候使用时髦,晦涩的功能是个好主意?通常情况下,人们最终会放入UNIVERSAL命名空间以避免继承问题。

(但如果你认为这是一个糟糕的答案,那么另一个downvote会给我一个同伴压力徽章:D)