barplot - ¿Cómo limpio correctamente un objeto de Python?
pandas plot (9)
Aquí es un esqueleto de trabajo mínimo:
class SkeletonFixture:
def __init__(self):
pass
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def method(self):
pass
with SkeletonFixture() as fixture:
fixture.method()
Importante: volver a ti mismo
Si eres como yo, y pasas por alto la parte de return self
(de la respuesta correcta de Clint Miller ), estarás mirando esta tontería:
Traceback (most recent call last):
File "tests/simplestpossible.py", line 17, in <module>
fixture.method()
AttributeError: ''NoneType'' object has no attribute ''method''
Pasé medio día en esto. Espero que ayude a la siguiente persona.
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 que Python no garantiza la existencia de "variables globales" (¿datos de miembros en este contexto?) Cuando se __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?
Como un apéndice a la respuesta de Clint , puede simplificar PackageResource
usando contextlib.contextmanager
:
@contextlib.contextmanager
def packageResource():
class Package:
...
package = Package()
yield package
package.cleanup()
Alternativamente, aunque probablemente no sea Pythonic, puede anular el 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 usar with Package(...) as package
.
Para contextlib.closing
, close
su función de limpieza y use contextlib.closing
, en cuyo caso puede usar la clase Package
sin modificar a 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
Creo que el problema podría estar en __init__
si hay más código del que se muestra?
__del__
se llamará incluso cuando __init__
no se haya ejecutado correctamente o se haya __init__
una excepción.
La forma estándar es usar 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 debe tener en cuenta que esto persistirá en todas las instancias creadas de Package
hasta que Python finalice.
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...
No creo que sea posible eliminar miembros de instancia antes de que se __del__
. Mi conjetura sería que la razón de tu AttributeError en particular está en otra parte (tal vez, por error, elimines self.file en otra parte).
Sin embargo, como señalaron los demás, debe evitar usar __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 refcount 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. (Aunque podría estar equivocado acerca de todo esto, tendría que leer los documentos de gc otra vez, pero estoy bastante seguro de que funciona así).
Parece que la forma idiomática de hacerlo es proporcionar un método close()
(o similar) y llamarlo explícitamente.
Recomiendo usar Python''s with
declaración para administrar recursos que necesitan ser limpiados. El problema con el uso de una declaración de close()
explícita es que debe preocuparse por que las personas se olviden de llamarla o se olviden de colocarla en un bloque final para evitar una fuga de recursos cuando se produce una excepción.
Para usar la instrucción with
, cree una clase con los siguientes métodos:
def __enter__(self)
def __exit__(self, exc_type, exc_value, traceback)
En tu ejemplo anterior, usarías
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)
Luego, cuando alguien quisiera usar tu clase, harían lo siguiente:
with Package() as package_obj:
# use package_obj
La variable package_obj será una instancia de tipo Package (es el valor devuelto por el método __enter__
). Su método __exit__
se llamará automáticamente, independientemente de si se produce o no una excepción.
Incluso podrías llevar este enfoque un paso más allá. En el ejemplo anterior, alguien aún podría crear una instancia del Paquete usando su constructor sin usar la cláusula with
. No quieres que eso suceda. Puede solucionar esto creando una clase PackageResource que define los métodos __enter__
y __exit__
. Luego, la clase de Paquete se definirá estrictamente dentro del método __enter__
y se devolverá. De esa manera, la persona que llama nunca podría crear una instancia de la clase Package sin usar una instrucció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()
Usaría esto de la siguiente manera:
with PackageResource() as package_obj:
# use package_obj
Simplemente envuelva su destructor con una declaración try / except y no lanzará 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
Rellenará la lista de archivos en la función del que se garantiza que existe en el momento de la llamada. El proxy débil es para evitar que Python, o usted mismo elimine la variable self.files de alguna manera (si se elimina, no afectará a la lista de archivos original). Si no es el caso de que se esté eliminando aunque haya más referencias a la variable, puede eliminar la encapsulación de proxy.
Una mejor alternativa es usar weakref.finalize . Vea los ejemplos en Finalizer Objects y Comparando los finalizadores con los métodos __del __ () .