Qual è il metodo __del__, come chiamarlo?


108

Sto leggendo un codice. C'è una classe in cui __del__è definito il metodo. Ho capito che questo metodo viene utilizzato per distruggere un'istanza della classe. Tuttavia, non riesco a trovare un luogo in cui viene utilizzato questo metodo. La ragione principale di ciò è che io non so come si usa questo metodo, probabilmente non così: obj1.del(). Quindi, le mie domande sono come chiamare il __del__metodo?

Risposte:


168

__del__è un finalizzatore . Viene chiamato quando un oggetto viene sottoposto a garbage collection, il che accade ad un certo punto dopo che tutti i riferimenti all'oggetto sono stati eliminati.

In un semplice caso questo potrebbe essere subito dopo aver detto del xo, se xè una variabile locale, dopo che la funzione termina. In particolare, a meno che non ci siano riferimenti circolari, CPython (l'implementazione standard di Python) effettuerà immediatamente la garbage collection.

Tuttavia, questo è un dettaglio dell'implementazione di CPython. L'unica proprietà richiesta della garbage collection di Python è che accade dopo che tutti i riferimenti sono stati cancellati, quindi questo potrebbe non essere necessario accadere subito dopo e potrebbe non accadere affatto .

Inoltre , le variabili possono vivere a lungo per molte ragioni , ad esempio un'eccezione che si propaga o l'introspezione del modulo può mantenere il conteggio dei riferimenti delle variabili maggiore di 0. Inoltre, la variabile può far parte del ciclo dei riferimenti - CPython con la raccolta dei rifiuti attivata si interrompe di più , ma non tutti, tali cicli, e anche allora solo periodicamente.

Dal momento che non hai alcuna garanzia che sia eseguito, non si dovrebbe mai mettere il codice in cui devi essere eseguito __del__()- invece, questo codice appartiene alla finallyclausola del tryblocco oa un gestore di contesto in withun'istruzione. Tuttavia, ci sono casi d'uso validi per __del__: ad esempio, se un oggetto fa Xriferimento Ye mantiene anche una copia del Yriferimento in un global cache( cache['X -> Y'] = Y), allora sarebbe corretto X.__del__eliminare anche la voce della cache.

Se si sa che il distruttore fornisce (in violazione delle linee guida sopra) una pulizia necessaria, si potrebbe desiderare di chiamarlo direttamente , poiché non v'è niente di speciale come un metodo: x.__del__(). Ovviamente dovresti farlo solo se sai che non gli dispiace essere chiamato due volte. Oppure, come ultima risorsa, puoi ridefinire questo metodo utilizzando

type(x).__del__ = my_safe_cleanup_method  

5
Dici che la caratteristica di CPython di eliminare un oggetto immediatamente dopo che il suo conteggio dei riferimenti è stato ridotto a zero è un "dettaglio di implementazione". Non sono convinto. Potete fornire un collegamento a sostegno di tale affermazione? (Voglio dire, il carattere in grassetto è abbastanza convincente da solo, ma i collegamenti sono un secondo vicino ... :-)
Stuart Berg

14
Dettagli sull'implementazione di CPython: CPython attualmente utilizza uno schema di conteggio dei riferimenti con (opzionale) rilevamento ritardato di spazzatura collegata ciclicamente, ... Altre implementazioni agiscono in modo diverso e CPython potrebbe cambiare. ( docs.python.org/2/reference/datamodel.html )
ilya n.

Che cosa c'è __exit__in questo contesto? È rincorso o prima __del__o insieme?
lony

1
"Potrebbe non accadere affatto" include quando il programma termina?
Andy Hayden

1
@ AndyHayden: i __del__metodi potrebbero non essere eseguiti anche al termine del programma, e anche quando vengono eseguiti al termine, scrivere un __del__metodo che funzioni correttamente anche mentre l'interprete è impegnato ad autodistruggersi intorno a te richiede una codifica più attenta rispetto a molti programmatori. (La pulizia CPython di solito ottiene __del__metodi da eseguire all'arresto dell'interprete, ma ci sono ancora casi in cui non è sufficiente. Thread daemon, globali di livello C e oggetti __del__creati in un altro __del__possono portare a __del__metodi non in esecuzione.)
user2357112 supporta Monica

80

Ho scritto la risposta per un'altra domanda, anche se questa è una domanda più accurata.

Come funzionano i costruttori e i distruttori?

Ecco una risposta un po 'supponente.

Non usare __del__. Questo non è C ++ o un linguaggio creato per i distruttori. Il __del__metodo dovrebbe davvero essere andato in Python 3.x, anche se sono sicuro che qualcuno troverà un caso d'uso che abbia senso. Se è necessario utilizzare __del__, essere consapevoli delle limitazioni di base per http://docs.python.org/reference/datamodel.html :

  • __del__viene chiamato quando capita che il garbage collector raccolga gli oggetti, non quando si perde l'ultimo riferimento a un oggetto e non quando si esegue del object.
  • __del__è responsabile della chiamata di qualsiasi __del__in una superclasse, sebbene non sia chiaro se questo sia in ordine di risoluzione del metodo (MRO) o semplicemente chiamando ogni superclasse.
  • Avere un __del__mezzo per cui il Garbage Collector rinuncia a rilevare e pulire eventuali collegamenti ciclici, ad esempio perdere l'ultimo riferimento a un elenco collegato. Puoi ottenere un elenco degli oggetti ignorati da gc.garbage. A volte puoi usare riferimenti deboli per evitare del tutto il ciclo. Questo viene dibattuto di tanto in tanto: vedere http://mail.python.org/pipermail/python-ideas/2009-October/006194.html .
  • La __del__funzione può barare, salvare un riferimento a un oggetto e interrompere la raccolta dei rifiuti.
  • Le eccezioni esplicitamente sollevate in __del__vengono ignorate.
  • __del__complementi __new__molto più di __init__. Questo crea confusione. Vedi http://www.algorithm.co.il/blogs/programming/python-gotchas-1- del -is-not-the-opposto-di- init / per una spiegazione e trucchi.
  • __del__non è un bambino "molto amato" in Python. Noterai che la documentazione di sys.exit () non specifica se la spazzatura viene raccolta prima di uscire e ci sono molti problemi strani. Il richiamo di __del__on globals causa strani problemi di ordinamento, ad esempio http://bugs.python.org/issue5099 . Dovrebbe essere __del__chiamato anche se __init__fallisce? Vedi http://mail.python.org/pipermail/python-dev/2000-March/thread.html#2423 per un thread lungo.

Ma d'altra parte:

E la mia ragione principale per non apprezzare la __del__funzione.

  • Ogni volta che qualcuno lo solleva, __del__si trasforma in trenta messaggi di confusione.
  • Rompe questi elementi nello Zen di Python:
    • Semplice è meglio che complicato.
    • I casi speciali non sono abbastanza speciali da infrangere le regole.
    • Gli errori non dovrebbero mai passare in silenzio.
    • Di fronte all'ambiguità, rifiuta la tentazione di indovinare.
    • Dovrebbe esserci un modo ovvio, e preferibilmente uno solo, per farlo.
    • Se l'implementazione è difficile da spiegare, è una cattiva idea.

Quindi, trova un motivo per non usare __del__.


6
Anche se la domanda non è esattamente: perché non dovremmo usare __del__, ma come chiamare __del__, la tua risposta è interessante.
nbro

Grazie. A volte l'idea migliore è allontanarsi da idee orribili.
Charles Merriam

In altre notizie, ho dimenticato di menzionare che PyPy (un interprete più veloce per app più lunghe) si interromperà su del .
Charles Merriam

Grazie @Gloin per aver aggiornato il collegamento interrotto!
Charles Merriam

@CharlesMerriam Grazie voi per la risposta!
Tom Burrows

13

Il __del__metodo, verrà chiamato quando l'oggetto viene sottoposto a garbage collection. Nota che non è necessariamente garantito che venga chiamato. Il codice seguente da solo non lo farà necessariamente:

del obj

Il motivo è che deldiminuisce il conteggio dei riferimenti di uno. Se qualcos'altro ha un riferimento all'oggetto, __del__non verrà chiamato.

Tuttavia, ci sono alcuni avvertimenti sull'utilizzo __del__. In generale, di solito non sono molto utili. Mi sembra più che tu voglia usare un metodo ravvicinato o forse una dichiarazione con .

Vedi la documentazione di Python sui __del__metodi .

Un'altra cosa da notare: i __del__metodi possono inibire la raccolta dei rifiuti se abusati. In particolare, un riferimento circolare che ha più di un oggetto con un __del__metodo non verrà raccolto dalla spazzatura. Questo perché il garbage collector non sa quale chiamare per primo. Consulta la documentazione sul modulo gc per maggiori informazioni.


8

Il __del__metodo (nota l'ortografia!) Viene chiamato quando il tuo oggetto viene finalmente distrutto. Tecnicamente parlando (in cPython) cioè quando non ci sono più riferimenti al tuo oggetto, cioè quando esce dallo scope.

Se vuoi eliminare il tuo oggetto e quindi chiamare il __del__metodo usa

del obj1

che cancellerà l'oggetto (a condizione che non ci fossero altri riferimenti ad esso).

Ti suggerisco di scrivere una piccola classe come questa

class T:
    def __del__(self):
        print "deleted"

E indagare nell'interprete Python, ad es

>>> a = T()
>>> del a
deleted
>>> a = T()
>>> b = a
>>> del b
>>> del a
deleted
>>> def fn():
...     a = T()
...     print "exiting fn"
...
>>> fn()
exiting fn
deleted
>>>   

Nota che jython e ironpython hanno regole diverse per quanto riguarda esattamente quando l'oggetto viene cancellato e __del__viene chiamato. __del__Tuttavia, non è considerato una buona pratica da utilizzare a causa di ciò e del fatto che l'oggetto e il suo ambiente potrebbero trovarsi in uno stato sconosciuto quando viene chiamato. Non è assolutamente garantito __del__nemmeno che venga chiamato: l'interprete può uscire in vari modi senza eliminare tutti gli oggetti.


1
rispetto a stackoverflow.com/a/2452895/611007 e stackoverflow.com/a/1481512/611007 , use del obj1sembra una cattiva idea su cui fare affidamento.
n611x007

0

Come accennato in precedenza, la __del__funzionalità è alquanto inaffidabile. Nei casi in cui potrebbe sembrare utile, considera invece l'utilizzo dei metodi __enter__e __exit__. Questo darà un comportamento simile alla with open() as f: passsintassi usata per accedere ai file. __enter__viene chiamato automaticamente quando si entra nell'ambito di with, mentre __exit__viene chiamato automaticamente quando si esce. Vedi questa domanda per maggiori dettagli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.