一个鲜为人知的内置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)
总而言之,属性不会做太多,另一方面使它们变得非常灵活:你可以将它们用作真正的"属性"(如本例所示),实现类似装饰器的东西(参见思南的回答)或者你的自己的狡猾目的.
属性是如果你不知道如何使用它们的事情之一,你不应该打扰它们.我曾经创建了一个database_method
属性,向系统指示在进入此方法之前将请求记录集,并且该方法知道它的主要输入将来自它对应的存储过程.
我正在使用属性来包装具有该数据的实际指定操作.因此,一个真正看似有用的想法是使用间接包装方法,但是如果不重写它就更难使调用者工作.最后它作为一个"仅限专家"的功能非常明显,并且需要支持来追踪神秘的内脏 - 如果你在perl-also商店中编写Perl,你想要避免的事情.
人们可能想投票给我,但我从思南引用的文章中得出结论:
注意事项
虽然这是一种强大的技术,但它并不完美.代码将无法正确包装匿名子例程,并且它不一定将调用上下文传播到包装函数.此外,使用此技术将显着增加程序在运行时必须执行的子例程调度的数量.根据程序的复杂程度,这可能会显着增加调用堆栈的大小.如果炫目速度是一个主要的设计目标,这个策略可能不适合你.
除非您愿意覆盖,否则这些都是重大缺陷caller
.我并不关心"炫目的速度",而且我不愿意尝试超越caller
绕过任何将自己注册为"DO_NOT_REPORT"的子程序 - 但我有一些编码愚蠢但没有但也遭到了殴打.
即便是这篇文章也承认这个功能有多么缺乏记录,并且包含了这个警告.告诉我什么时候使用时髦,晦涩的功能是个好主意?通常情况下,人们最终会放入UNIVERSAL
命名空间以避免继承问题.
(但如果你认为这是一个糟糕的答案,那么另一个downvote会给我一个同行压力徽章:D)