2009-05-14 7 views
340
class Package: 
    def __init__(self): 
     self.files = [] 

    # ... 

    def __del__(self): 
     for file in self.files: 
      os.unlink(file) 

__del__(self) anterior falla con una excepción AttributeError. Entiendo Python doesn't guarantee la existencia de "variables globales" (datos de miembros en este contexto?) Cuando se invoca __del__(). Si ese es el caso y esta es la razón de la excepción, ¿cómo me aseguro de que el objeto se destruya correctamente?¿Cómo puedo limpiar correctamente un objeto de Python?

+3

Al leer lo que vinculó, las variables globales que desaparecen no parecen aplicarse aquí a menos que esté hablando de cuándo se está ejecutando el programa, durante el cual supongo que de acuerdo con lo que vinculó podría ser POSIBLE que el módulo del sistema operativo sea Ya se fue. De lo contrario, no creo que se aplique a las variables miembro en un método __del __(). –

+3

La excepción se produce mucho antes de que mi programa salga. La excepción AttributeError que recibo es que Python dice que no reconoce self.files como un atributo de Package. Puede que me esté equivocando, pero si por "globales" no quieren decir variables globales para los métodos (pero posiblemente de clase local a local), entonces no sé qué causa esta excepción. Google insinúa que Python se reserva el derecho de limpiar los datos de los miembros antes de llamar a __del__ (self). – wilhelmtell

+1

El código publicado parece funcionar para mí (con Python 2.5). ¿Puede publicar el código real que está fallando, o simplificado (cuanto más simple es la mejor versión que todavía causa el error? – Silverfish

Respuesta

468

Recomendaría usar la declaración with de Python para administrar los recursos que deben limpiarse. El problema con el uso de una declaración explícita de close() es que tiene que preocuparse de que las personas se olviden de llamarlo o se olviden de colocarlo en un bloque finally para evitar una pérdida de recursos cuando se produce una excepción.

utilizar la instrucción with, crear una clase con los siguientes métodos:

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

En el ejemplo anterior, tendrá que utilizar

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) 

Entonces, cuando alguien quería utilizar su clase , que harían lo siguiente:

with Package() as package_obj: 
    # use package_obj 

el package_obj variable será una instancia del tipo Paquete (es el valor devuelto por el método __enter__). Se llamará automáticamente a su método __exit__, independientemente de si se produce una excepción o no.

Incluso podría llevar este enfoque un paso más allá. En el ejemplo anterior, alguien aún podría instanciar el paquete usando su constructor sin usar la cláusula with. No quieres que eso suceda. Puede solucionarlo creando una clase PackageResource que defina los métodos __enter__ y __exit__. Entonces, la clase del paquete se definiría estrictamente dentro del método __enter__ y se devolvería.De esta manera, la persona que llama nunca podría crear una instancia de la clase del paquete sin necesidad de utilizar una declaración with:

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() 

que tendría que utilizar esto como sigue:

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

Técnicamente hablando, se podría llamar a PackageResource() .__ enter __() explícitamente y así crear un paquete que nunca se finalizaría ... pero realmente tendrían que intentar romper el código. Probablemente no sea algo de lo que preocuparse. –

+3

Por cierto, si está utilizando Python 2.5, necesitará hacer desde __future__ import with_statement para poder usar la instrucción with. –

+2

Encontré un artículo que ayuda a mostrar por qué __del __() actúa de la manera en que lo hace y da crédito a usar una solución de administrador de contexto: http://www.andy-pearce.com/blog/posts/2013/Apr/python- destructor-drawbacks/ – eikonomega

6

Simplemente envuelva su destructor con una instrucción try/except y no arrojará una excepción si sus globales ya están eliminados.

Editar

Prueba esto:

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 

Será rellenar la lista de archivos en el del función que está garantizado para existir en el momento de la llamada. El proxy weakref es para evitar que Python, o usted mismo elimine la variable self.files de alguna manera (si se elimina, entonces no afectará la lista de archivos original). Si no es el caso de que esto se elimine aunque haya más referencias a la variable, entonces puede eliminar la encapsulación del proxy.

+2

El problema es que si los datos del miembro se han ido, ya es demasiado tarde para mí. Necesito esa información. Ver mi código anterior: necesito los nombres de los archivos para saber qué archivos eliminar. Sin embargo, simplifiqué mi código, hay otros datos que necesito limpiar yo mismo (es decir, el intérprete no sabrá cómo limpiar). – wilhelmtell

+0

Esta solución funcionó muy bien en mi caso. – gaborous

4

Parece que la forma idiomática de hacer esto es proporcionar un método close() (o similar) y llamarlo explícitamente.

+18

Este es el enfoque que utilicé anteriormente, pero me encontré con otros problemas. Con excepciones lanzadas por todas partes por otras bibliotecas, necesito la ayuda de Python para limpiar el desorden en caso de error. Específicamente, necesito que Python llame al destructor en mi nombre, porque de lo contrario el código se vuelve rápidamente inmanejable, y seguramente olvidaré un punto de salida donde debería haber una llamada a .close(). – wilhelmtell

16

No creo que es posible que los miembros de instancia para eliminarse antes de llamar al __del__. Supongo que la razón de su AttributeError particular está en otra parte (tal vez erróneamente elimine self.file en otro lugar).

Sin embargo, como los otros señalaron, debe evitar el uso de __del__. La razón principal de esto es que las instancias con __del__ no se recolectarán como basura (solo se liberarán cuando su recuento llegue a 0). Por lo tanto, si sus instancias están involucradas en referencias circulares, vivirán en la memoria mientras se ejecute la aplicación. (Sin embargo, puedo estar equivocado acerca de todo esto, tendría que volver a leer los documentos de gc, pero estoy bastante seguro de que funciona así).

+4

Los objetos con '__del__' pueden ser basura recolectada si su recuento de referencia de otros objetos con' __del__' es cero y no están disponibles. Esto significa que si tienes un ciclo de referencia entre objetos con '__del__', ninguno de ellos será recolectado. Cualquier otro caso, sin embargo, debe resolverse como se espera. – Collin

10

Creo que el problema podría estar en __init__ si hay más código que el que se muestra?

__del__ se llamará incluso cuando __init__ no se ha ejecutado correctamente o inició una excepción.

Source

+2

Suena muy probable. La mejor manera de evitar este problema al usar '__del__' es declarar explícitamente a todos los miembros a nivel de clase, asegurándose de que siempre existan, incluso si' __init__' falla. En el ejemplo dado, 'files =()' funcionaría, aunque en su mayoría solo asignarías 'None'; en cualquier caso, aún necesita asignar el valor real en '__init__'. –

21

Como un apéndice a Clint's answer, puede simplificar PackageResource usando contextlib.contextmanager:

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

Como alternativa, aunque probablemente no tan Pythonic, puede anular 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): 
     ... 

y simplemente use with Package(...) as package.

para hacer las cosas más corto, el nombre de su función de limpieza close y utilizar contextlib.closing, en cuyo caso se puede utilizar la clase sin modificar Package través with contextlib.closing(Package(...)) o anular su __new__ a la más simple

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

Y este constructor se hereda , por lo que simplemente puede heredar, por ejemplo

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

** Esto es increíble. ** Me gusta particularmente el último ejemplo. Sin embargo, es desafortunado que no podamos evitar la repetición repetitiva de cuatro líneas del método 'Package .__ new __()'. "O tal vez podamos". Probablemente podríamos definir un decorador de clases o una metaclase que sea una genérica para nosotros. Comida para el pensamiento pitonico. –

+0

@CecilCurry Gracias, y buen punto. Cualquier clase que herede de 'Package' también debería hacer esto (aunque todavía no lo he probado), por lo que no debería requerirse metaclass. Aunque he encontrado algunas formas bastante curiosas de usar metaclases en el pasado ... –

+0

@CecilCurry En realidad, el constructor se hereda, por lo que puedes usar 'Package' (o mejor una clase llamada' Closing') como tu clase principal en lugar de 'objeto'. Pero no me preguntes cómo la herencia múltiple se equivoca con esto ... –

6

La forma habitual es utilizar 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) 

Pero se debe tener en cuenta que este persistirá todas las instancias creadas de Package hasta que se termina Python.

demo usando el código anterior guardado como 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 mejor alternativa es utilizar weakref.finalize. Vea los ejemplos en Finalizer Objects y Comparing finalizers with __del__() methods.

+0

Lo usaste hoy y funciona sin problemas, mejor que otras soluciones. Tengo una clase de comunicador de multiprocesamiento que abre un puerto serie y luego tengo un método 'stop()' para cerrar los puertos y 'join()' los procesos. Sin embargo, si el programa sale inesperadamente, 'stop()' no se llama, lo resolví con un finalizador. Pero en cualquier caso, llamo '_finalizer.detach()' en el método de detención para evitar llamarlo dos veces (manualmente y luego nuevamente por el finalizador). –

Cuestiones relacionadas