Vamos pegar algo muito simples,
# Foo.pm
package Foo {
my $baz = bar();
sub bar { 42 }; ## Overwrite this
print $baz; ## Before this is executed
}
Existe alguma maneira que eu possa test.pl
executar código que altera o que $baz
está definido e faz Foo.pm
com que imprima outra coisa na tela?
# maybe something here.
use Foo;
# maybe something here
É possível, com as fases do compilador, forçar a impressão acima 7
?
perl
compilation
Evan Carroll
fonte
fonte
Foo::bar
, masuse Foo
funcionará tanto a fase de compilação (redefinindo a barra, se alguma coisa foi definida anteriormente lá) quanto a fase de execução do Foo. A única coisa em que consigo pensar seria em um@INC
gancho profundamente hacky para modificar como o Foo é carregado.require
(e, portantouse
), compila e executa o módulo antes de retornar. O mesmo vale paraeval
.eval
não pode ser usado para compilar código sem também executá-lo.Respostas:
Um hack é necessário porque
require
(e, portanto,use
), compila e executa o módulo antes de retornar.O mesmo vale para
eval
.eval
não pode ser usado para compilar código sem também executá-lo.A solução menos intrusiva que encontrei seria substituir
DB::postponed
. Isso é chamado antes de avaliar um arquivo necessário compilado. Infelizmente, ele é chamado apenas durante a depuração (perl -d
).Outra solução seria ler o arquivo, modificá-lo e avaliar o arquivo modificado, como o seguinte:
O item acima não está configurado corretamente
%INC
, altera o nome do arquivo usado pelos avisos e, portanto, não chamaDB::postponed
etc. A seguir, é uma solução mais robusta:Eu usei
UNITCHECK
(que é chamado após a compilação, mas antes da execução) porque eu antecede a substituição (usandounread
) em vez de ler o arquivo inteiro e anexar a nova definição. Se você quiser usar essa abordagem, poderá obter um identificador de arquivo para retornar usandoParabéns ao @Grinnz por mencionar
@INC
ganchos.fonte
Como as únicas opções aqui serão profundamente hacky, o que realmente queremos aqui é executar o código após a adição da sub-rotina ao
%Foo::
stash:fonte
Isso emitirá alguns avisos, mas imprime 7:
Primeiro, nós definimos
Foo::bar
. Seu valor será redefinido pela declaração em Foo.pm, mas o aviso "Subrotine Foo :: bar redefined" será acionado, o que chamará o manipulador de sinal que redefine a sub-rotina novamente para retornar 7.fonte
perl -w
.Aqui está uma solução que combina a conexão do processo de carregamento do módulo com os recursos de criação somente leitura do módulo Readonly:
fonte
Revisei minha solução aqui, para que ela não se baseie mais
Readonly.pm
, depois de saber que havia perdido uma alternativa muito simples, com base na resposta do m-conrad , que reformulei na abordagem modular que comecei aqui.Foo.pm (O mesmo que no post de abertura )
OverrideSubs.pm Atualizado
test-run.pl
Execução e saída:
fonte
Se o
sub bar
interiorFoo.pm
tiver um protótipo diferente de umaFoo::bar
função existente , o Perl não o substituirá? Esse parece ser o caso e torna a solução bastante simples:ou tipo a mesma coisa
Atualização: não, a razão pela qual isso funciona é que o Perl não redefinirá uma sub-rotina "constante" (com protótipo
()
); portanto, essa é apenas uma solução viável se a sua função simulada for constante.fonte
BEGIN { *Foo::bar = sub () { 7 } }
é melhor escrito comosub Foo::bar() { 7 }
sub bar { 42 } my $baz = bar();
vez demy $baz = bar(); sub bar { 42 }
, não funcionaria.Prototype mismatch: sub Foo::bar () vs none at Foo.pm line 5.
eConstant subroutine bar redefined at Foo.pm line 5.
)Vamos ter um concurso de golfe!
Isso apenas prefixa o código do módulo com a substituição do método, que será a primeira linha de código executada após a fase de compilação e antes da fase de execução.
Em seguida, preencha a
%INC
entrada para que cargas futurasuse Foo
não puxem a original.fonte