Perl方法属性如何工作?

Nik*_*nko 46 perl attributes

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

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

tre*_*els 37

你是对的,文档在这方面不是很清楚,特别是因为属性并不那么复杂.如果定义子例程属性,如下所示:

sub some_method :Foo { }
Run Code Online (Sandbox Code Playgroud)

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

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

FETCH_CODE_ATTRIBUTES当有人说时,会调用另一个处理程序

use attributes;
my @attrs = attributes::get(\&some_method);
Run Code Online (Sandbox Code Playgroud)

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

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

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;
Run Code Online (Sandbox Code Playgroud)

现在,在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
Run Code Online (Sandbox Code Playgroud)

总而言之,属性不会做太多,另一方面使它们变得非常灵活:你可以将它们用作真正的"属性"(如本例所示),实现类似装饰器的东西(参见思南的回答)或者你的自己的狡猾目的.


Axe*_*man 5

属性是如果你不知道如何使用它们的事情之一,你不应该打扰它们.我曾经创建了一个database_method属性,向系统指示在进入此方法之前将请求记录集,并且该方法知道它的主要输入将来自它对应的存储过程.

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


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

注意事项

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

除非您愿意覆盖,否则这些都是重大缺陷caller.我并不关心"炫目的速度",而且我不愿意尝试超越caller绕过任何将自己注册为"DO_NOT_REPORT"的子程序 - 但我有一些编码愚蠢但没有但也遭到了殴打.

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

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

  • 是的,但这些警告仅适用于使用属性的这种特殊方式,即包装原始方法.在大多数使用属性的情况下(催化剂等),它们仅用于标记(我认为),这根本不存在问题. (2认同)