Pull to refresh

Атрибуты: взгляд внутрь

Reading time 4 min
Views 2.1K
Это продолжение статьи "Введение в атрибуты". Если Вы не знакомы с идеей атрибутов и их синтаксисом — советую начать с нее. Ну а в этой статье рассматривается, как устроены атрибуты изнутри, как с ними обращаться, и какие могут возникнуть проблемы.

Guts


Когда во время компиляции Perl встречает атрибут, он пытается вызвать у текущего класса один из хендлеров вида MODIFY_SCALAR_ATTRIBUTES, MODIFY_CODE_ATTRIBUTES и т.д. — в зависимости от типа помеченных атрибутом данных, примерно так:

__PACKAGE__->MODIFY_CODE_ATTRIBUTES(\&mySub, 'myAttribute1', 'myAttribute2');

Еще раз повторюсь: эти вызовы выполняются прямо во время компиляции (на стадии BEGIN), а точнее — сразу после того, как perl докурит закрывающую функцию фигурную скобку (справедливый UPD от xames: не все хэндлы вида MODIFY_*_ATTRIBUTES выполняются на стадии BEGIN. В частности MODIFY_SCALAR_ATTRIBUTES выполнится на этапе инициализации переменной (my $tmp: attr = 0;)). Поскольку атрибутов может быть несколько, а наследование никто не отменял, считается хорошим тоном прошерстить внутри хендлера полученный список, отобрать оттуда те атрибуты, которые обработать можно, и отдать суперклассу на растерзание то, что осталось.

Аналогично, если вы хотите получить список атрибутов функции/переменной и вызываете для этого функцию get из модуля attributes, происходит обращение к хендлеру FETCH_SCALAR_ATTRIBUTES (ну или FETCH_CODE_ATTRIBUTES — думаю, вы уже поняли).

Собственно, все особенности работы с атрибутами на низком уровне можно посмотреть в perldoc attributes. Кстати, для тех, кому такая реализация показалась кривоватой, там есть важное замечание: "The mechanisms described here are still experimental. Do not rely on the current implementation". Ну что ж, нет ничего постояннее экспериментальных механизмов :-)

Рецепт счастья


Очевидно, ковырять атрибуты при помощи вышеупомянутых хендлеров — занятие не слишком-то приятное. Слава Богу, в поставке того же Perl 5.6 появился модуль Attribute::Handlers, который значительно упрощает написание обработчиков для атрибутов и вводит дополнительные интересные возможности — атрибуты могут иметь параметры и обрабатываться не только во время BEGIN-стадии, но и во время CHECK, INIT, END. Кстати, работает этот модуль опять же при помощи атрибутов.

Итак, чтобы можно было использовать атрибут myAttribute для пометки функций, достаточно написать следующий код:

use Attribute::Handlers;
sub myAttribute : ATTR(CODE) {
my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenum) = @_;
....
}


Обработчик — метод myAttribute — будет вызван для помеченной функции приблизительно так же, как и хендлеры из предыдущих примеров. Параметров ему передается заметно больше: кроме ссылки на функцию и имени атрибута могут присутствовать также ссылка на элемент таблицы символов (GLOB), название фазы BEGIN/CHECK/INIT/END, дополнительные параметры атрибута, указание на имя файла и номер строки. По умолчанию обработчик запускается во время стадии компиляции CHECK, когда все что можно уже загружено и переварено.

Если нам захочется передавать обработчику дополнительную информацию — можно сделать это например так:

sub mySub : myAttribute(p1,p2) {:}

В этом случае обработчик получит в переменную $data массив со значениями p1 и p2.

Из других забавных возможностей Attribute::Handlers стоит отметить альтернативный интерфейс к функции tie, который сам по себе является довольно интересным примером применения атрибутов. Останавливаться на нем здесь особого смысла нет, все предельно понятно из документации.

Стоит отметить, что большинство модулей CPAN, использующих атрибуты, опирается именно на модуль Attribute::Handlers. Тем не менее, поможет он не всегда. Более того, не надейтесь что все так гладко и красиво, как кажется :)

Грабли


Фаза CHECK не зря выбрана в Attribute::Handlers, как дефолтная. На этой фазе интерпретатор уже закончил обработку кода и разместил функции и все остальное в таблице символов, так что теперь есть техническая возможность получить ссылку на соответствующий GLOB и пошаманить над ним — например, подменить функцию. Сделать это на фазе BEGIN не получится — GLOB еще не заполнен, и в обработчик атрибута передан не будет. И вот тут начинается самое интересное. Если вы занимаетесь разработкой под mod_perl, все это к вам не относится — у вас нет фазы CHECK. Согласно perldoc perlmod, внутри вызовов eval и под mod_perl стадии компиляции CHECK и INIT не работают, есть только BEGIN и еще UNITCHECK. С UNITCHECK'ом тоже не все гладко — глобально эта фаза не перехватывается, только на уровне соответствующего модуля. Мне не удалось найти модуля, который бы решал эту проблему и давал возможность добраться до GLOB'а под mod_perl столь же тривиально, как это можно сделать в обычном скрипте. Можно было бы добавить на уровне Attribute::Handlers возможность вводить свои собственные триггеры для запуска обработчиков и дергать их вручную — но модуль написан так, что про подобные патчи можно сразу забыть. Я смог решить эту задачу фактически введя дополнительное ограничение при написании кода — пришлось отказаться от require при загрузке классов в пользу use. В результате работа делается в два захода — сначала на этапе BEGIN собирается список функций, помеченных нужным мне атрибутом, а после вызова метода import делается основная работа. Если кто-то предложит более человечный способ, я буду рад.

Заключение


По сути, атрибуты позволяют привязать к привычным составляющим программы дополнительные возможности. Интерес этих возможностей в том, что на самом деле их в коде не видно. Это чем-то похоже на tie — вы выполняете элементарные действия над простейшими структурами данных, за которыми на самом деле скрыты нетривиальные обработчики. Если вам понадобился некий универсальный механизм, но вы не хотите, чтобы особенности его реализации были видны в коде — посмотрите в сторону атрибутов.
Tags:
Hubs:
+25
Comments 14
Comments Comments 14

Articles