2015-03-20 5 views
7

Dato il seguente programma Perl:Perché la mia ridefinizione localizzata di un sub di un pacchetto non ha effetto?

package Foo; 
use strict; 
use warnings; 

sub new { 
    my ($class) = @_; 
    return bless {}, $class; 
} 

sub c { 
    print "IN ORIG C\n"; 
} 

sub DESTROY { 
    print "IN DESTROY\n"; 
    c(); 
} 

1; 

package main; 
use strict; 
use warnings; 
no warnings qw/redefine once/; 

local *Foo::c = sub { print "IN MY C\n" }; 
my $f = Foo->new(); 
undef $f; 

mi aspetto di output come:

IN DESTROY 
IN MY C 

Ma io realmente ottenere output come:

IN DESTROY 
IN ORIG C 

Q: Perché la mia ridefinizione localizzata di Foo::c non ha effetto?

risposta

2

Questo è un bug in perl che verrà corretto in 5.22 (vedere il commento di Leon sotto).

Questo accade perché undef $f; in realtà non liberare e distruggere $f, solo che lo contrassegna come pronti per essere liberati da un nextstate op.

nextstate ops esistono approssimativamente tra ogni istruzione, e ci sono per pulire lo stack, tra le altre cose.

Nel tuo esempio, dal momento che undef $f è l'ultima cosa nel file, non vi c'è NextState dopo di esso, in modo che il distruttore locale esce dallo scope prima $f s' distruttore si chiama (o, la distruzione globale che guarda caso non è a conoscenza del tuo cambiamento locale.)

Quando si aggiunge una dichiarazione di stampa dopo undef $f, il nextstate op prima che la stampa chiama il distruttore locale.

È possibile visualizzare l'ulteriore nextstate nell'esempio https://gist.github.com/calid/aeb939147fdd171cffe3#file-04-diff-concise-out.

È anche possibile vedere questo comportamento controllando caller() nel metodo DESTROY:

sub DESTROY { 
    my ($pkg, $file, $line) = caller; 
    print "Destroyed at $pkg, $file, $line\n"; 
    c(); 
} 

[email protected]:~$ perl foo.pl 
Destroyed at main, foo.pl, 0 
IN DESTROY 
IN ORIG C 

[email protected]:~$ echo 'print "hi\n"' >> foo.pl 
[email protected]:~$ perl foo.pl 
Destroyed at main, foo.pl, 30 
IN DESTROY 
IN MY C 
hi 

(Linea 30 è il print "hi\n")

speranza che getta luce su questo.

Cheers.

+1

Ancora meglio, questo problema è stato risolto in blead lo scorso agosto e la correzione sarà in 5.22 http: //perl5.git. perl.org/perl.git/commitdiff/4dda930be –

6

Quando il codice perl viene compilato, i globi per variabili/simboli del pacchetto vengono cercati (e creati come necessario) e fanno riferimento direttamente dal codice compilato.

Pertanto, quando si sostituisce (temporaneamente) la voce della tabella dei simboli per *Foo::c in fase di runtime, tutto il codice già compilato che utilizzava *Foo::c utilizza ancora il glob originale. Ma fare/require'd code o eval STRING o riferimenti simbolici no.

(Molto simile al Access package variable after its name is removed from symbol table in Perl?, vedere gli esempi lì.)

+0

ysth dopo aver giocato con il codice La risposta di Rob sembra essere corretta in questo caso, sei d'accordo? – calid

+0

nessuno dei due ha ragione. 'local * glob' in realtà non sostituisce il glob stesso, sostituisce semplicemente il GV nel glob, quindi il mio ragionamento è sbagliato e la localizzazione dovrebbe funzionare. ma il DESTROY dovrebbe funzionare nello scope, al momento di undef; sembra essere un bug che non * solo quando l'undef è l'ultima cosa nel blocco * – ysth

+0

temporaneamente non ripristinabile così calid può vedere questo – ysth

2

Il problema qui non ha a che fare con il tempo di compilazione vs runtime ma piuttosto con scoping.

L'utilizzo di local limita l'ambito del tuo Foo::c modificato al resto dell'ambito corrente (che nell'esempio è il resto del tuo script). Ma DESTROY non viene eseguito in tale ambito, anche quando si specifica esplicitamente undef $f (Vedere http://perldoc.perl.org/perlobj.html#Destructors per ulteriori informazioni sul comportamento di DESTROY). Funziona a un tempo indeterminato più tardi, in particolare DOPO $ f è "andato fuori portata". Pertanto, tutte le modifiche apportate a local apportate nell'ambito di $ f non verranno applicate ogni volta che viene eseguito DESTROY.

Potete vedere voi stessi, semplicemente rimuovendo il local nel tuo esempio:

Con local

IN DESTROY 
IN ORIG C 

Senza local

IN DESTROY 
IN MY C 

o aggiungendo un paio di subroutine aggiuntive e chiamarli in ambito package::main:

package Foo; 
... 
sub d { 
    c(); 
} 

sub DESTROY { 
    print "IN DESTROY\n"; 
    c(); 
} 

1; 

package main; 
... 
sub e { 
    Foo::c(); 
} 

local *Foo::c = sub { print "IN MY C\n" }; 
my $f = Foo->new(); 
Foo::c(); 
Foo::d(); 
e(); 
undef $f; 

che stampa

IN MY C 
IN MY C 
IN MY C 
IN DESTROY 
IN ORIG C 

Quindi, solo nel DISTRUGGERE è il c originale utilizzato, inoltre dimostrando che questo è un problema di scoping.

Vedere anche https://stackoverflow.com/a/19100461/232706 per una spiegazione completa delle regole di scoping Perl.

+0

Rob I didn Troviamo il collegamento perldoc/SO tutto ciò che è utile per questa particolare domanda (né discute esplicitamente l'ambito di DESTROY che è diverso dall'ambito dell'istanziazione). Pensi che abbia senso rimuoverli o sostituirli con quelli più rilevanti? – calid

+0

DESTROY funziona quasi sicuramente al momento della undef $ f (prova 'perl -wE'package Foo {sub nuovo {bless {}} sub DESTROY {dì" DESTROY "}} my $ x = Foo-> nuovo; undef $ x; dì "done" ") così è nello scope dinamico del locale – ysth

+0

ma sembra che ci sia un bug quando undef è l'ultima cosa nel blocco che sta producendo il risultato indesiderato – ysth

Problemi correlati