Можно ли перехватить вызовы метода Perl?
Можно ли перехватить вызов метода в Perl, сделать что-то с аргументами, а затем выполнить его?
Ответы
Ответ 1
Да, вы можете перехватить вызовы подпрограммы Perl. У меня есть целая глава об этом в Освоение Perl. Ознакомьтесь с модулем Hook:: LexWrap, который позволяет вам делать это, не просматривая все детали. Perl-методы - это просто подпрограммы.
Вы также можете создать подкласс и переопределить метод, который хотите поймать. Это немного лучший способ сделать это, потому что то, как объектно-ориентированное программирование хочет, чтобы вы с ним делали. Однако иногда люди пишут код, который не позволяет вам делать это правильно. Там больше об этом в Освоение Perl".
Ответ 2
Чтобы кратко описать, 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
Это выглядит как работа для Moose! Moose - это объектная система для Perl, которая может это сделать и многое другое. docs будет намного лучше объяснять, чем я могу, но то, что вы, скорее всего, захотите, это Модификатор метода, в частности before
.
Ответ 4
Вы можете, и Павел описывает хороший способ сделать это, но вы, вероятно, должны уточнить, почему вы хотите сделать это в первую очередь.
Если вы ищете расширенные способы перехвата вызовов произвольным подпрограммам, то работа с таблицами символов будет работать для вас, но если вы хотите добавить функциональность к функциям, возможно, экспортированным в пространство имен, в котором вы сейчас работаете, тогда вам может понадобиться знать способы вызова функций, которые существуют в других пространствах имен.
Данные:: Дампер, например, обычно экспортирует функцию "Дампер" в вызывающее пространство имен, но вы можете переопределить или отключить ее и предоставить свою собственную функцию Dumper, которая затем вызывает оригинал с полным именем.
например.
use Data::Dumper;
sub Dumper {
warn 'Dumping variables';
print Data::Dumper::Dumper(@_);
}
my $foo = {
bar => 'barval',
};
Dumper($foo);
Опять же, это альтернативное решение, которое может быть более подходящим в зависимости от исходной проблемы. При игре с таблицей символов можно весело провести время, но это может быть чрезмерным и может привести к сложному поддержанию кода, если он вам не нужен.
Ответ 5
Да.
Вам нужно три вещи:
Аргументы вызова находятся в @_
, который является еще одной переменной с динамической областью.
Затем 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
и go:
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