Non capisco questo comportamento __del__ di python



3 Answers

Sto fornendo la mia risposta perché, mentre apprezzo il consiglio di evitare __del__ , la mia domanda era come farla funzionare correttamente per l'esempio di codice fornito.

Versione breve: il codice seguente utilizza weakref per evitare il riferimento circolare. Pensavo di averlo provato prima di pubblicare la domanda, ma suppongo di aver sbagliato qualcosa.

import types, weakref

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"

Versione più lunga : quando ho postato la domanda, ho cercato domande simili. So che puoi usare invece, e che il sentimento prevalente è che __del__ è BAD .

Usare with senso ha, ma solo in certe situazioni. Aprire un file, leggerlo e chiuderlo è un buon esempio dove with è una soluzione perfettamente buona. Sei passato a uno specifico blocco di codice in cui è necessario l'oggetto e vuoi ripulire l'oggetto e la fine del blocco.

Una connessione al database sembra essere usata spesso come un esempio che non funziona bene with , dal momento che di solito devi lasciare la sezione di codice che crea la connessione e avere la connessione chiusa in un evento più guidato (piuttosto che sequenziale) lasso di tempo.

Se with non è la soluzione giusta, vedo due alternative:

  1. Assicurati che __del__ (vedi questo blog per una descrizione migliore dell'uso di weakref)
  2. Si utilizza il modulo atexit per eseguire una richiamata quando il programma si chiude. Vedi questo argomento per esempio.

Mentre cercavo di fornire un codice semplificato, il mio vero problema è più basato sugli eventi, quindi non è una soluzione appropriata ( with il codice semplificato per il codice semplificato). Volevo anche evitare atexit , in quanto il mio programma può essere di lunga esecuzione e voglio essere in grado di eseguire la pulizia il prima possibile.

Quindi, in questo caso specifico, trovo che sia la soluzione migliore per usare weakref e prevenire riferimenti circolari che impediscano il __del__ di __del__ .

Questa potrebbe essere un'eccezione alla regola, ma ci sono casi d'uso in cui l'uso di weakref e __del__ è la giusta implementazione, IMHO.

python del

Qualcuno può spiegare perché il seguente codice si comporta come fa:

import types

class Dummy():
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print "delete",self.name

d1 = Dummy("d1")
del d1
d1 = None
print "after d1"

d2 = Dummy("d2")
def func(self):
    print "func called"
d2.func = types.MethodType(func, d2)
d2.func()
del d2
d2 = None
print "after d2"

d3 = Dummy("d3")
def func(self):
    print "func called"
d3.func = types.MethodType(func, d3)
d3.func()
d3.func = None
del d3
d3 = None
print "after d3"

L'output (si noti che il distruttore per d2 non viene mai chiamato) è questo (python 2.7)

delete d1
after d1
func called
after d2
func called
delete d3
after d3

C'è un modo per "sistemare" il codice in modo che il distruttore venga chiamato senza cancellare il metodo aggiunto? Voglio dire, il posto migliore per mettere il d2.func = None sarebbe nel distruttore!

Grazie

[modifica] Sulla base delle prime poche risposte, vorrei chiarire che non sto chiedendo dei meriti (o meno) dell'uso di __del__ . Ho cercato di creare la funzione più breve che dimostrasse quello che considero un comportamento non intuitivo. Presumo che sia stato creato un riferimento circolare, ma non sono sicuro del perché. Se possibile, mi piacerebbe sapere come evitare il riferimento circolare ....




del non chiama __del__

del nel modo in cui si utilizza rimuove una variabile locale. __del__ viene chiamato quando l'oggetto viene distrutto. Python come linguaggio non garantisce quando distruggerà un oggetto.

CPython è l'implementazione più comune di Python, utilizza il conteggio dei riferimenti. Di conseguenza, spesso funzionerà come ci si aspetta. Tuttavia non funzionerà nel caso in cui tu abbia un ciclo di riferimento.

d3 -> d3.func -> d3

Python non rileva questo e quindi non lo pulirà subito. E non sono solo cicli di riferimento. Se viene lanciata un'eccezione, probabilmente vorrai comunque chiamare il tuo distruttore. Tuttavia, Python manterrà generalmente le variabili locali come parte del suo traceback.

La soluzione non dipende dal metodo __del__ . Piuttosto, usa un gestore di contesto.

class Dummy:
   def __enter__(self):
       return self

   def __exit__(self, type, value, traceback):
       print "Destroying", self

with Dummy() as dummy:
    # Do whatever you want with dummy in here
# __exit__ will be called before you get here

Questo è garantito per funzionare, e puoi anche controllare i parametri per vedere se stai gestendo un'eccezione e fare qualcosa di diverso in quel caso.




Mi sembra che il vero cuore della questione sia qui:

aggiungendo le funzioni è dinamico (in fase di esecuzione) e non noto in anticipo

Sento che quello che cerchi davvero è un modo flessibile per associare funzionalità diverse a un oggetto che rappresenta lo stato del programma. Conosciuto anche come polimorfismo. Python lo fa abbastanza bene, non collegando / scollegando metodi, ma istanziando classi diverse. Ti suggerisco di guardare di nuovo la tua organizzazione di classe. Forse è necessario separare un oggetto dati core e persistente da oggetti di stato transitori. Usa il paradigma has-a piuttosto che is-a: ogni volta che lo stato cambia, puoi avvolgere i dati principali in un oggetto stato o assegnare il nuovo oggetto stato a un attributo del core.

Se sei sicuro di non poter usare quel tipo di OOP pythonic, potresti comunque aggirare il tuo problema in un altro modo definendo tutte le tue funzioni nella classe e successivamente associandole ad altri attributi di istanza (a meno che tu non stia compilando queste funzioni al volo dall'input dell'utente):

class LongRunning(object):
    def bark_loudly(self):
        print("WOOF WOOF")

    def bark_softly(self):
        pring("woof woof")


while True:
    d = LongRunning()
    d.bark = d.bark_loudly
    d.bark()

    d.bark = d.bark_softly
    d.bark()





Related


Tags

python   del