Как работают атрибуты метода Perl? - PullRequest
46 голосов
/ 12 июня 2009

Мало известной встроенной функцией Perl являются атрибуты. Тем не менее, официальная документация довольно плохо справляется с внедрением новичков в концепцию. В то же время фреймворки, такие как Catalyst, широко используют атрибуты, что, кажется, упрощает многие вещи. Поскольку использование чего-либо, не зная последствий, немного отстой, я хотел бы знать детали. Синтаксически они выглядят как декораторы Python, но документация подразумевает нечто более простое.

Не могли бы вы объяснить (на реальных примерах, если это возможно), какие атрибуты хороши и что происходит за дверями?

Ответы [ 3 ]

37 голосов
/ 12 июня 2009

Вы правы, документация не очень понятна в этой области, тем более что атрибуты не так сложны. Если вы определите атрибут подпрограммы, например так:

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 ответ ), или для ваших собственных коварных целей.

11 голосов
/ 12 июня 2009
5 голосов
/ 12 июня 2009

Атрибуты - это одна из вещей, которая, если вы не знаете, как их использовать, вам не стоит беспокоиться о них. Однажды я создал атрибут database_method, чтобы указать системе, что набор записей будет запрошен перед входом в этот метод и что метод знал, что его основные входные данные будут поступать из хранимой процедуры, которой он соответствует.

Я использовал атрибуты, чтобы обернуть действительные, указанные действия с этими данными. Так что одна из действительно полезных на первый взгляд идей заключается в том, чтобы обернуть методы косвенно, но заставить вызывающую работу работать сложнее, не переопределяя ее. В конце концов, он был слишком видимым как функция «только для экспертов», и ему потребовалась бы поддержка для отслеживания тайных внутренностей - чего-то, чего вы хотите избежать, если пишете Perl в магазине perl-также.


Люди могут хотеть голосовать за меня, но я беру из статьи, цитируемой Синаном:

Предостережения

Хотя это мощная техника, она не идеальна. Код не будет правильно оборачивать анонимные подпрограммы , а не обязательно распространяет контекст вызова на обернутые функции . Кроме того, использование этого метода значительно увеличит количество подпрограмм, которые ваша программа должна выполнить во время выполнения. В зависимости от сложности вашей программы это может значительно увеличить размер стека вызовов. Если основная цель дизайна - ослепительная скорость, эта стратегия может быть не для вас.

Это существенные недостатки, если вы не хотите переопределить caller. Меня не слишком волнует "скорость ослепления", и я наполовину готов попробовать свои силы в переопределении caller, чтобы обойти любую подпрограмму, которая регистрируется как "DO_NOT_REPORT" - но у меня есть некоторая безрассудность кодирования, которая меня тоже еще не выбили.

Даже статья признает, насколько плохо документирована эта функция, и содержит это предупреждение. Скажите, когда еще было бы неплохо использовать шикарную, неясную функцию? Довольно часто люди в конечном итоге помещают пространство имен UNIVERSAL, чтобы избежать проблемы наследования.

(Но если вы думаете, что это плохой ответ, просто еще одно понижение голосов даст мне знак давления со стороны сверстников: D)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...