2009-09-01 5 views
14

Можете ли вы перехватить вызов метода в Perl, сделать что-то с аргументами, а затем выполнить его?Можно ли перехватить вызовы метода Perl?

+1

Я вижу, что вы использовали тег «aop». Почему бы не спросить о конкретных методах АОП, которые вы хотели бы использовать? (Ответы на ваш вопрос могут помочь вам внедрить систему АОП, но зачем это делать, когда кто-то еще это сделал?) – jrockway

+0

Я думал, что это можно сделать с помощью атрибутов метода, но теперь я не могу найти соответствующую страницу perldoc (perldoc-атрибуты это не та страница, о которой я думал). Я вспоминаю хороший пример, включающий в себя обертывание различных методов для добавления в журнал ... – Ether

+0

См. Этот связанный вопрос SO для дополнительной информации: http://stackoverflow.com/questions/635212/how-i-redefine-perl-class-methods – draegtun

ответ

14

Да, вы можете перехватить вызовы подпрограммы Perl. У меня есть целая глава о таких вещах в Mastering Perl. Проверьте модуль Hook::LexWrap, который позволяет вам делать это, не просматривая все детали. Методы Perl - это просто подпрограммы.

Вы также можете создать подкласс и переопределить метод, который хотите поймать. Это немного лучший способ сделать это, потому что именно так хочет объектно-ориентированное программирование. Однако иногда люди пишут код, который не позволяет вам делать это правильно. Об этом в Mastering Perl.

6

Это выглядит как работа для Moose! Moose - это объектная система для Perl, которая может это сделать и многое другое. docs будет намного лучше объяснять, чем я могу, но то, что вы, скорее всего, захотите, это Method Modifier, в частности before.

+1

Moose - это то, что вы используете для всего приложения, а не для целенаправленных проблем. –

+3

Но это зависит от того, в чем проблема, и у задающегося вопросом вопроса нет никаких подробностей. Возможно, кто-то оценит подход, который следует предпринять при создании нового приложения, и в этом случае его включение в Муз будет подходящим и полезным ответом. – jsoverson

+1

Мы только что сказали то же самое. Я не сказал, чтобы не использовать Муса, но я не сказал, чтобы использовать его. –

7

Чтобы кратко описать, Perl обладает способностью изменять таблицу символов. Вы вызываете подпрограмму (метод) через таблицу символов пакета, к которой принадлежит метод. Если вы измените таблицу символов (и это не считается очень грязным), вы можете заменить большинство вызовов методов вызовом других методов, которые вы указали. Это демонстрирует подход:

# The subroutine we'll interrupt calls to 
sub call_me 
{ 
    print shift,"\n"; 
} 

# Intercepting factory 
sub aspectate 
{ 
    my $callee = shift; 
    my $value = shift; 
    return sub { $callee->($value + shift); }; 
} 
my $aspectated_call_me = aspectate \&call_me, 100; 

# Rewrite symbol table of main package (lasts to the end of the block). 
# Replace "main" with the name of the package (class) you're intercepting 
local *main::call_me = $aspectated_call_me; 

# Voila! Prints 105! 
call_me(5); 

Это также показывает, что, когда кто-то берет ссылку подпрограммы и называет его по ссылке, вы больше не можете влиять на такие вызовы.

Я уверен, что существуют рамки для аксессуаров в perl, но это, я надеюсь, демонстрирует подход.

3

Да.

Вам нужны три вещи:

аргументы для вызова в @_, который является еще одним динамической областью видимости переменной.

Затем goto поддерживает аргумент reference-sub, который сохраняет текущий @_, но выполняет другой (хвостовой) вызов функции.

Окончательно local может использоваться для создания глобальных переменных с лексической границей, а таблицы символов похоронены в %::.

Итак, вы получили:

sub foo { 
    my($x,$y)=(@_); 
    print "$x/$y = " . ((0.0+$x)/$y)."\n"; 
} 
sub doit { 
    foo(3,4); 
} 
doit(); 

, которые, конечно, распечатывает:

3/4 = 0.75 

Мы можем заменить Foo с помощью local и перейти:

my $oldfoo = \&foo; 
local *foo = sub { (@_)=($_[1], $_[0]); goto $oldfoo; }; 
doit(); 

И теперь мы получаем :

4/3 = 1.33333333333333 

Если вы хотите изменить *foo без использования его имени, и вы не хотите использовать eval, то вы можете изменить его, манипулируя %::, например:

$::{"foo"} = sub { (@_)=($_[0], 1); goto $oldfoo; }; 
doit(); 

И теперь мы получаем:

3/1 = 3 
5

Вы можете, и Павел описывает хороший способ сделать это, но вам, вероятно, следует уточнить, почему вы хотите сделать это в первую очередь.

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

Данные: например, Dumper экспортирует функцию «Dumper» в вызывающее пространство имен, но вы можете переопределить или отключить ее и предоставить свою собственную функцию Dumper, которая затем вызывает оригинал с полным именем.

например.

use Data::Dumper; 

sub Dumper { 
    warn 'Dumping variables'; 
    print Data::Dumper::Dumper(@_); 
} 

my $foo = { 
    bar => 'barval', 
}; 

Dumper($foo); 

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

+1

В этом случае вы должны дать пустой список импорта Dumper, чтобы вы ничего не импортировали. Кроме того, последовательность имеет значение в этом случае. Сначала нужно импортировать, затем переопределить. Определяется последнее определение подпрограммы. Наконец, под предупреждениями вы получите сообщение об ошибке «redefine». –

+0

Определенно, просто стараюсь, чтобы это было просто для примера. Прямое имеет большую ценность (и быстрее для ответчика), чем объяснение каждой касательной детали. Концепции импорта и предупреждения лучше всего будут объяснять в качестве других вопросов. – jsoverson