Как я могу обезвредить метод экземпляра в Perl?
Я пытаюсь обезглавить (duck-punch:-) a LWP::UserAgent
экземпляр, например:
sub _user_agent_get_basic_credentials_patch {
return ($username, $password);
}
my $agent = LWP::UserAgent->new();
$agent->get_basic_credentials = _user_agent_get_basic_credentials_patch;
Это не правильный синтаксис - он дает:
Невозможно изменить функцию non-lvalue вызовите на [module] line [lineno].
Как я помню (из программирования Perl), диспетчерский поиск выполняется динамически на основе благословенного пакета (ref($agent)
, я считаю), поэтому я не уверен, как патч для установки обезьян будет работать даже без влияния на благословенный пакет.
Я знаю, что я могу подклассифицировать UserAgent
, но я бы предпочел более сжатый подход, дополненный обезьянами. Согласие взрослых и что у вас есть.; -)
Ответы
Ответ 1
Если динамическая область (с использованием local
) не является удовлетворительной, вы можете автоматизировать технику аннулирования пользовательского пакета:
MONKEY_PATCH_INSTANCE:
{
my $counter = 1; # could use a state var in perl 5.10
sub monkey_patch_instance
{
my($instance, $method, $code) = @_;
my $package = ref($instance) . '::MonkeyPatch' . $counter++;
no strict 'refs';
@{$package . '::ISA'} = (ref($instance));
*{$package . '::' . $method} = $code;
bless $_[0], $package; # sneaky re-bless of aliased argument
}
}
Пример использования:
package Dog;
sub new { bless {}, shift }
sub speak { print "woof!\n" }
...
package main;
my $dog1 = Dog->new;
my $dog2 = Dog->new;
monkey_patch_instance($dog2, speak => sub { print "yap!\n" });
$dog1->speak; # woof!
$dog2->speak; # yap!
Ответ 2
Как сказано Fayland Lam, правильный синтаксис:
local *LWP::UserAgent::get_basic_credentials = sub {
return ( $username, $password );
};
Но это исправление (динамически ограниченное) всего класса, а не только экземпляр. Вы, вероятно, можете избежать этого в своем случае.
Если вы действительно хотите повлиять только на экземпляр, используйте описанное ниже подклассу. Это можно сделать "на лету" следующим образом:
{
package My::LWP::UserAgent;
our @ISA = qw/LWP::UserAgent/;
sub get_basic_credentials {
return ( $username, $password );
};
# ... and rebless $agent into current package
$agent = bless $agent;
}
Ответ 3
sub _user_agent_get_basic_credentials_patch {
return ($username, $password);
}
my $agent = LWP::UserAgent->new();
$agent->get_basic_credentials = _user_agent_get_basic_credentials_patch;
У вас нет 1, но 2 проблемы здесь, потому что это то, что вы делаете:
( $agent->get_basic_credentials() ) = _user_agent_get_basic_credentials_patch();
по обеим сторонам, вы вызываете субтитры, а не просто ссылаетесь на них.
assign the result of
'_user_agent_get_basic_credentials_patch'
to the value that was returned from
'get_basic_credentials';
Эквивалентная логика:
{
package FooBar;
sub foo(){
return 5;
}
1;
}
my $x = bless( {}, "FooBar" );
sub baz(){
return 1;
}
$x->foo() = baz();
# 5 = 1;
Так что неудивительно, что он жалуется.
Ваш "фиксированный" код в вашем ответе также неверен по той же причине, с другой проблемой, которую вы не можете реализовать:
$agent->{get_basic_credentials} = _user_agent_get_basic_credentials_patch;
Это довольно ошибочное логическое мышление, которое работает так, как вы думаете.
Что он действительно делает, это:
1. Dereference $agent, which is a HashRef
2. Set the hash-key 'get_basic_credentials' to the result from _user_agent_get_basic_credentials_patch
Вы не назначили какую-либо функцию вообще.
{
package FooBar;
sub foo(){
return 5;
}
1;
}
my $x = bless( {}, "FooBar" );
sub baz(){
return 1;
}
$x->{foo} = baz();
# $x is now = ( bless{ foo => 1 }, "FooBar" );
# $x->foo(); # still returns 5
# $x->{foo}; # returns 1;
Патч обезьян, конечно, довольно злой, и я сам не видел, как переопределить метод в единственном экземпляре чего-то подобного.
Однако, что вы можете сделать, это следующее:
{
no strict 'refs';
*{'LWP::UserAgent::get_basic_credentials'} = sub {
# code here
};
}
Что бы глобально заменило поведение разделов кода get_basic_credentials (возможно, я ошибаюсь, кто-то меня исправляет)
Если вам действительно нужно сделать это на основе каждого экземпляра, вы, вероятно, можете немного наследовать класс и просто построить производный класс вместо этого и/или динамически создавать новые пакеты.
Ответ 4
В духе Perl, "делающего трудные вещи", здесь приведен пример того, как делать патч обезьяны одного экземпляра без наложения наследования.
Я НЕ рекомендую вам делать это в любом коде, который кто-либо еще должен будет поддерживать, отлаживать или зависеть (как вы сказали, соглашаясь с взрослыми):
#!/usr/bin/perl
use strict;
use warnings;
{
package Monkey;
sub new { return bless {}, shift }
sub bar { return 'you called ' . __PACKAGE__ . '::bar' }
}
use Scalar::Util qw(refaddr);
my $f = Monkey->new;
my $g = Monkey->new;
my $h = Monkey->new;
print $f->bar, "\n"; # prints "you called Monkey::bar"
monkey_patch( $f, 'bar', sub { "you, sir, are an ape" } );
monkey_patch( $g, 'bar', sub { "you, also, are an ape" } );
print $f->bar, "\n"; # prints "you, sir, are an ape"
print $g->bar, "\n"; # prints "you, also, are an ape"
print $h->bar, "\n"; # prints "you called Monkey::bar"
my %originals;
my %monkeys;
sub monkey_patch {
my ( $obj, $method, $new ) = @_;
my $package = ref($obj);
$originals{$method} ||= $obj->can($method) or die "no method $method in $package";
no strict 'refs';
no warnings 'redefine';
$monkeys{ refaddr($obj) }->{$method} = $new;
*{ $package . '::' . $method } = sub {
if ( my $monkey_patch = $monkeys{ refaddr( $_[0] ) }->{$method} ) {
return $monkey_patch->(@_);
} else {
return $originals{$method}->(@_);
}
};
}
Ответ 5
Perl думает, что вы пытаетесь вызвать подпрограмму слева от задания, поэтому он жалуется. Я думаю, что вы можете напрямую ударить таблицу символов Perl (используя *LWP::UserAgent::get_basic_credentials
или что-то еще), но мне не хватает Perl-fu, чтобы правильно сделать это заклинание.
Ответ 6
http://www.google.com/codesearch/p?hl=en#tgg5_3LXifM/Authen-Simple-HTTP-0.1/lib/Authen/Simple/HTTP.pm&q=get_basic_credentials
Ответ 7
Основываясь на Ответ Джона Сиракузы... Я обнаружил, что мне все еще нужна ссылка на оригинальную функцию. Поэтому я сделал это:
MONKEY_PATCH_INSTANCE:
{
my $counter = 1; # could use a state var in perl 5.10
sub monkey_patch_instance
{
my($instance, $method, $code) = @_;
my $package = ref($instance) . '::MonkeyPatch' . $counter++;
no strict 'refs';
my $oldFunction = \&{ref($instance).'::'.$method};
@{$package . '::ISA'} = (ref($instance));
*{$package . '::' . $method} = sub {
my ($self, @args) = @_;
$code->($self, $oldFunction, @args);
};
bless $_[0], $package; # sneaky re-bless of aliased argument
}
}
# let say you have a database handle, $dbh
# but you want to add code before and after $dbh->prepare("SELECT 1");
monkey_patch_instance($dbh, prepare => sub {
my ($self, $oldFunction, @args) = @_;
print "Monkey patch (before)\n";
my $output = $oldFunction->(($self, @args));
print "Monkey patch (after)\n";
return $output;
});
Это то же самое, что и в исходном ответе, за исключением того, что я пропускаю некоторые параметры $self
и $oldFunction
.
Это позволяет нам вызывать $self
$oldFunction
, как обычно, но украшать над ним дополнительный код.
Ответ 8
Изменить: Это была некорректная попытка решения, которое я сохраняю для потомков. Посмотрите на одобренные/принятые ответы.: -)
А, я просто понял, что синтаксис нуждается в некоторой корректировке:
$agent->{get_basic_credentials} = _user_agent_get_basic_credentials_patch;
Без разделителей {}
он выглядит как вызов метода (который не будет действительным значением l).
Мне все равно хотелось бы узнать, как метод экземпляра связан с этим синтаксисом. ТИА!