2009-05-14 11 views
340

__del__(self) non riesce con un'eccezione AttributeError. Capisco Python doesn't guarantee l'esistenza di "variabili globali" (dati dei membri in questo contesto?) Quando viene invocato il numero __del__(). Se questo è il caso e questo è il motivo dell'eccezione, come posso assicurarmi che l'oggetto si distrugga correttamente?Come ripulire correttamente un oggetto Python?

+3

Leggere ciò che hai collegato, le variabili globali che vanno via non sembra applicarsi qui a meno che tu non stia parlando di quando stai programmando di uscire, durante il quale suppongo che in base a ciò che hai collegato potrebbe essere POSSIBILE che il modulo os stesso sia già andato. Altrimenti, non penso che si applichi alle variabili membro in un metodo __del __(). –

+3

L'eccezione viene lanciata molto prima che il mio programma venga chiuso. L'eccezione AttributeError che ottengo è che Python dice che non riconosce self.files come un attributo di Package. Potrei sbagliarmi, ma se per "globals" non significano variabili globali ai metodi (ma possibilmente locali alla classe), allora non so cosa causi questa eccezione. Indizi di Google Python si riserva il diritto di pulire i dati dei membri prima che venga chiamato __del __ (self). – wilhelmtell

+1

Il codice pubblicato sembra funzionare per me (con Python 2.5). Puoi pubblicare il codice effettivo che sta fallendo o semplificato (più semplice è la versione che causa ancora l'errore? – Silverfish

risposta

468

Si consiglia di utilizzare la dichiarazione with di Python per la gestione delle risorse che devono essere ripulite. Il problema con l'utilizzo di un'esplicita dichiarazione close() è che devi preoccuparti che le persone si dimentichino di chiamarlo o dimenticare di inserirlo in un blocco finally per impedire una perdita di risorse quando si verifica un'eccezione.

utilizzare l'istruzione with, creare una classe con i seguenti metodi:

def __enter__(self) 
    def __exit__(self, exc_type, exc_value, traceback) 

Nel tuo esempio sopra, utilizza

class Package: 
    def __init__(self): 
     self.files = [] 

    def __enter__(self): 
     return self 

    # ... 

    def __exit__(self, exc_type, exc_value, traceback): 
     for file in self.files: 
      os.unlink(file) 

Poi, quando qualcuno ha voluto usare la tua classe , farebbero quanto segue:

with Package() as package_obj: 
    # use package_obj 

La variabile package_obj sarà un istanza di tipo Pacchetto (è il valore restituito dal metodo __enter__). Il suo metodo __exit__ verrà chiamato automaticamente, indipendentemente dal fatto che si verifichi o meno un'eccezione.

Si potrebbe anche prendere questo approccio un ulteriore passo avanti. Nell'esempio sopra, qualcuno potrebbe ancora istanziare Package usando il suo costruttore senza usare la clausola with. Non vuoi che ciò accada. È possibile risolvere questo problema creando una classe PackageResource che definisce i metodi __enter__ e __exit__. Quindi, la classe Package verrà definita rigorosamente all'interno del metodo __enter__ e restituita.In questo modo, il chiamante non potrebbe creare un'istanza della classe pacchetto senza utilizzare un with dichiarazione:

class PackageResource: 
    def __enter__(self): 
     class Package: 
      ... 
     self.package_obj = Package() 
     return self.package_obj 

    def __exit__(self, exc_type, exc_value, traceback): 
     self.package_obj.cleanup() 

devi usare questo come segue:

with PackageResource() as package_obj: 
    # use package_obj 
+25

Tecnicamente parlando, si potrebbe chiamare PackageResource() .__ inserire __() in modo esplicito e quindi creare un pacchetto che non sarebbe mai finalizzato ... ma dovevano davvero cercare di infrangere il codice. Probabilmente non è qualcosa di cui preoccuparsi. –

+3

A proposito, se stai usando Python 2.5, dovrai fare da __future__ import with_statement per poter usare l'istruzione with. –

+2

Ho trovato un articolo che aiuta a mostrare perché __del __() agisce come fa e dà credito all'utilizzo di una soluzione di gestione del contesto: http://www.andy-pearce.com/blog/posts/2013/Apr/python- distruttore-svantaggi/ – eikonomega

6

Basta avvolgere il distruttore con un'istruzione try/except e non genererà un'eccezione se i globals sono già eliminati.

Modifica

Prova questa:

from weakref import proxy 

class MyList(list): pass 

class Package: 
    def __init__(self): 
     self.__del__.im_func.files = MyList([1,2,3,4]) 
     self.files = proxy(self.__del__.im_func.files) 

    def __del__(self): 
     print self.__del__.im_func.files 

Sarà roba l'elenco dei file nella del funzionamento che è garantito per esistere al momento della chiamata. Il proxy weakref è di impedire a Python, o se stessi, di eliminare in qualche modo la variabile self.files (se viene cancellata, quindi non avrà effetto sull'elenco dei file originale). Se non è il caso in cui questo viene eliminato anche se ci sono più riferimenti alla variabile, è possibile rimuovere l'incapsulamento del proxy.

+2

Il problema è che se i dati dei membri sono spariti è troppo tardi per me. Ho bisogno di quei dati. Vedi il mio codice qui sopra: ho bisogno dei nomi dei file per sapere quali file rimuovere. Ho semplificato il mio codice, ma ci sono altri dati che ho bisogno di ripulire da solo (cioè l'interprete non saprà come pulire). – wilhelmtell

+0

Questa soluzione ha funzionato molto bene nel mio caso. – gaborous

4

Sembra che il modo idiomatico per fare ciò sia fornire un metodo close() (o simile) e chiamarlo esplicitamente.

+18

Questo è l'approccio che ho usato prima, ma mi sono imbattuto in altri problemi. Con le eccezioni lanciate dappertutto da altre librerie, ho bisogno dell'aiuto di Python per ripulire il casino in caso di errore. In particolare, ho bisogno che Python chiami il distruttore per me, perché altrimenti il ​​codice diventa rapidamente ingestibile, e sicuramente dimenticherò un punto di uscita dove dovrebbe essere una chiamata a .close(). – wilhelmtell

16

Non credo che sia possibile per i membri di istanza da rimuovere prima che venga chiamato __del__. La mia ipotesi sarebbe che la ragione del tuo particolare AttributeError è da qualche altra parte (forse rimuovi per errore self.file altrove).

Tuttavia, come hanno sottolineato gli altri, evitare di utilizzare __del__. La ragione principale di ciò è che le istanze con __del__ non verranno raccolte automaticamente (verranno liberate solo quando il loro account raggiunge 0). Pertanto, se le tue istanze sono coinvolte in riferimenti circolari, vivranno in memoria fino a quando l'applicazione verrà eseguita. (Potrei sbagliarmi su tutto questo però, dovrei leggere di nuovo i documenti gc, ma sono piuttosto sicuro che funzioni così).

+4

Gli oggetti con '__del__' possono essere garbage collection se il loro conteggio di riferimento da altri oggetti con' __del__' è zero e sono irraggiungibili. Questo significa che se hai un ciclo di riferimento tra oggetti con '__del__', nessuno di questi verrà raccolto. Qualsiasi altro caso, tuttavia, dovrebbe essere risolto come previsto. – Collin

10

Penso che il problema potrebbe essere in __init__ se c'è più codice di quello mostrato?

__del__ verrà chiamato anche se __init__ non è stato eseguito correttamente o ha generato un'eccezione.

Source

+2

I suoni sono molto probabili. Il modo migliore per evitare questo problema quando si usa '__del__' è dichiarare esplicitamente tutti i membri a livello di classe, assicurandosi che esistano sempre, anche se' __init__' fallisce. Nell'esempio fornito, 'files =()' funzionerebbe, anche se principalmente assegneresti 'None'; in entrambi i casi, è comunque necessario assegnare il valore reale in '__init__'. –

21

Come appendice a Clint's answer, è possibile semplificare PackageResource utilizzando contextlib.contextmanager:

@contextlib.contextmanager 
def packageResource(): 
    class Package: 
     ... 
    package = Package() 
    yield package 
    package.cleanup() 

In alternativa, anche se probabilmente non come Pythonic, è possibile ignorare Package.__new__:

class Package(object): 
    def __new__(cls, *args, **kwargs): 
     @contextlib.contextmanager 
     def packageResource(): 
      # adapt arguments if superclass takes some! 
      package = super(Package, cls).__new__(cls) 
      package.__init__(*args, **kwargs) 
      yield package 
      package.cleanup() 

    def __init__(self, *args, **kwargs): 
     ... 

e utilizzare semplicemente with Package(...) as package.

per ottenere le cose più breve, denominare la funzione di pulizia close e utilizzare contextlib.closing, nel qual caso è possibile utilizzare la non modificato Package classe tramite with contextlib.closing(Package(...)) o ignorare la sua __new__ al semplice

class Package(object): 
    def __new__(cls, *args, **kwargs): 
     package = super(Package, cls).__new__(cls) 
     package.__init__(*args, **kwargs) 
     return contextlib.closing(package) 

E questo costruttore viene ereditato , quindi puoi semplicemente ereditare, ad es

class SubPackage(Package): 
    def close(self): 
     pass 
+1

** Questo è fantastico. ** Mi piace particolarmente l'ultimo esempio. È sfortunato che non possiamo evitare la combinazione di quattro righe del metodo 'Package .__ new __()', tuttavia. O forse possiamo. Potremmo probabilmente definire un decoratore di classe o una metaclasse che generano per noi questo standard. Cibo per il pensiero pitonico. –

+0

@CecilCurry Grazie, e buon punto. Anche qualsiasi classe che eredita da 'Package' dovrebbe farlo (sebbene non l'abbia ancora testata), quindi non dovrebbe essere richiesto alcun metaclass. Anche se ho trovato alcuni modi piuttosto curiosi di utilizzare le metaclassi in passato ... –

+0

@CecilCurry In realtà, il costruttore è ereditato, quindi puoi usare 'Pacchetto' (o meglio una classe chiamata' Closing') come genitore di classe invece di 'object'. Ma non chiedermi in che modo l'ereditarietà multipla incasini questo ... –

6

Il metodo standard è quello di utilizzare atexit.register:

# package.py 
import atexit 
import os 

class Package: 
    def __init__(self): 
     self.files = [] 
     atexit.register(self.cleanup) 

    def cleanup(self): 
     print("Running cleanup...") 
     for file in self.files: 
      print("Unlinking file: {}".format(file)) 
      # os.unlink(file) 

Ma si dovrebbe tenere a mente che questo persisterà tutte le istanze create su Package fino Python è terminato.

Demo utilizzando il codice di cui sopra salvato package.py:

$ python 
>>> from package import * 
>>> p = Package() 
>>> q = Package() 
>>> q.files = ['a', 'b', 'c'] 
>>> quit() 
Running cleanup... 
Unlinking file: a 
Unlinking file: b 
Unlinking file: c 
Running cleanup... 
3

Una migliore alternativa è quella di utilizzare weakref.finalize. Vedere gli esempi allo Finalizer Objects e allo Comparing finalizers with __del__() methods.

+0

Usato così oggi e funziona perfettamente, meglio di altre soluzioni. Ho una classe di comunicatore basata su multiprocessing che apre una porta seriale e quindi ho un metodo 'stop()' per chiudere le porte e 'join()' i processi. Tuttavia, se i programmi si chiudono inaspettatamente "stop()" non viene chiamato - l'ho risolto con un finalizzatore. Ma in ogni caso chiamo '_finalizer.detach()' nel metodo stop per evitare di chiamarlo due volte (manualmente e successivamente dal finalizzatore). –

Problemi correlati