2009-06-12 91 views
45

一个鲜为人知的内置Perl特性是属性。然而,官方documentation做了一个相当糟糕的工作,引入新概念。与此同时,像Catalyst这样的框架广泛使用属性,这似乎使许多事情变得更容易。由于在不知道含义的情况下使用某些内容会让人感到厌倦,所以我想知道细节。语法方面,它们看起来像Python的装饰器,但文档意味着更简单。Perl方法属性如何工作?

你能解释一下(如果可能的话,用真实世界的例子)什么属性是好的,门后会发生什么?

回答

36

你说得对,这方面的文档不是很清楚,特别是因为属性并不那么复杂。如果定义了一个子程序的属性,就像这样:

sub some_method :Foo { } 

Perl将在编译程序时(这很重要)寻找魔术子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),或为了你自己的迂回目的。

5

属性是如果你不知道如何使用它们的事情之一,你不应该打扰他们。我曾经做过一个database_method属性,向系统表明在进入该方法之前将要求记录集,并且该方法知道它的主要输入来自它所对应的存储过程。

我正在使用属性来包装与该数据的实际,指定的操作。因此,其中一个看起来很有用的想法是用间接方式来包装方法,但在不重写它的情况下更难做出调用者的工作。最后,它作为一个“专家专用”功能显而易见,并且需要支持通过奥术内部进行追踪 - 如果您在perl-shop中编写Perl,则需要避免这种情况。


人们可能想投我失望,但我从文章引用思南采取:

注意事项

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

这些显著缺点,除非你愿意重写caller。我并不关心“致盲速度”,而且我也乐于尝试着重写caller以绕过任何注册自己为“DO_NOT_REPORT”的子例程 - 但是我有一些编码的愚蠢行为,还没有被打败我。

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

(但是,如果你认为这是一个不好回答,只是一个downvote会给我一个同龄人的压力徽章:d)

+2

确实如此,但这些注意事项仅适用于使用属性的特定方式,即包装原始方法。在大多数情况下,使用属性(催化剂等),它们只是用于标记(我认为),根本没有问题。 – trendels 2009-06-12 16:11:24

+0

尽管这是作为最清晰的Perl教程之一的主要用途之一(本来可以节省一些时间的)。我不得不承认,我还没有登记到催化剂。 – Axeman 2009-06-12 16:39:14

11
+1

@brian感谢您的纠正。我要指责选择性的认知,从那个页面上的作者列表中选择你的名字;-)现在我再看一遍,很明显Mike Friedman是作者。 – 2009-06-12 23:50:16