Python: cierres y clases
Pregunta
Tengo que registrar una función atexit
para su uso con una clase (ver Foo
abajo para un ejemplo) que, por desgracia, no tengo forma directa de limpieza a través de una llamada al método: otro código, que no tengo el control más, las llamadas Foo.start()
y Foo.end()
pero a veces no llama Foo.end()
si se encuentra un error, así que tengo que limpiar mi mismo.
Yo podría utilizar algunos consejos sobre los cierres en este contexto:
class Foo:
def cleanup(self):
# do something here
def start(self):
def do_cleanup():
self.cleanup()
atexit.register(do_cleanup)
def end(self):
# cleanup is no longer necessary... how do we unregister?
-
¿Funcionará el cierre adecuadamente, por ejemplo, en
do_cleanup
, es el valor de la auto ligado correctamente? -
¿Cómo puedo anular el registro de una rutina de atexit ()?
-
¿Hay una mejor manera de hacer esto?
editar : se trata de Python 2.6.5
Solución
Hacer un registro de un registro global y una función que llama a una función en ella, y sacarlos de allí cuando sea necesario.
cleaners = set()
def _call_cleaners():
for cleaner in list(cleaners):
cleaner()
atexit.register(_call_cleaners)
class Foo(object):
def cleanup(self):
if self.cleaned:
raise RuntimeError("ALREADY CLEANED")
self.cleaned = True
def start(self):
self.cleaned = False
cleaners.add(self.cleanup)
def end(self):
self.cleanup()
cleaners.remove(self.cleanup)
Otros consejos
self
está enlazado correctamente dentro de la devolución de llamada a do_cleanup, pero, de hecho, si todo lo que está haciendo es llamar al método que también podría utilizar el método vinculado directamente.
Se utiliza atexit.unregister()
para eliminar la devolución de llamada, pero hay un problema aquí ya que debe cancelar el registro de la misma función que se ha registrado y puesto que utilizó una función anidada que significa que tiene que almacenar una referencia a esa función. Si usted sigue mi sugerencia de utilizar un método vinculado a continuación, usted todavía tiene que guardar una referencia a él:
class Foo:
def cleanup(self):
# do something here
def start(self):
self._cleanup = self.cleanup # Need to save the bound method for unregister
atexit.register(self._cleanup)
def end(self):
atexit.unregister(self._cleanup)
Tenga en cuenta que todavía es posible que el código para salir sin llamar ther funciones atexit
registrada, por ejemplo, si el proceso se interrumpe con ctrl + ruptura en las ventanas o muerto con SIGABRT en Linux.
También como otra respuesta sugiere que sólo podría utilizar __del__
pero que puede ser problemático para la limpieza, mientras que un programa está saliendo, ya que no se puede llamar hasta después de otras variables globales que necesita para el acceso se han eliminado.
Editado tener en cuenta que cuando escribí esta respuesta la pregunta no especificó Python 2.x Oh, bueno, voy a dejar la respuesta aquí de todos modos, en caso de que ayuda a nadie más.
Desde shanked borra su publicación, voy a hablar en favor de __del__
nuevo:
import atexit, weakref
class Handler:
def __init__(self, obj):
self.obj = weakref.ref(obj)
def cleanup(self):
if self.obj is not None:
obj = self.obj()
if obj is not None:
obj.cleanup()
class Foo:
def __init__(self):
self.start()
def cleanup(self):
print "cleanup"
self.cleanup_handler = None
def start(self):
self.cleanup_handler = Handler(self)
atexit.register(self.cleanup_handler.cleanup)
def end(self):
if self.cleanup_handler is None:
return
self.cleanup_handler.obj = None
self.cleanup()
def __del__(self):
self.end()
a1=Foo()
a1.end()
a1=Foo()
a2=Foo()
del a2
a3=Foo()
a3.m=a3
Esto apoya los siguientes casos:
- objetos donde .end se llama con regularidad; la limpieza de inmediato
- objetos que se lanzan sin .end que se llama; la limpieza cuando el último referencia desaparece
- objetos vivos en ciclos; atexit limpieza
- objetos que se mantienen vivas; atexit limpieza
Tenga en cuenta que es importante que el manejador de limpieza contiene una referencia débil al objeto, ya que de lo contrario sería mantener el objeto vivo.
Editar : Los ciclos en los que interviene Foo no será recolección de basura, ya que Foo implementos __del__
. Para tener en cuenta el ciclo de eliminarse en el momento de la recolección de basura, la limpieza debe ser sacado del ciclo.
class Cleanup:
cleaned = False
def cleanup(self):
if self.cleaned:
return
print "cleanup"
self.cleaned = True
def __del__(self):
self.cleanup()
class Foo:
def __init__(self):...
def start(self):
self.cleaner = Cleanup()
atexit.register(Handler(self).cleanup)
def cleanup(self):
self.cleaner.cleanup()
def end(self):
self.cleanup()
Es importante que el objeto de Limpieza No hay referencias volver a Foo.
Creo que el código está bien. No hay manera de anular el registro, pero se puede establecer un indicador booleano que imposibilitaría la limpieza:
class Foo:
def __init__(self):
self.need_cleanup = True
def cleanup(self):
# do something here
print 'clean up'
def start(self):
def do_cleanup():
if self.need_cleanup:
self.cleanup()
atexit.register(do_cleanup)
def end(self):
# cleanup is no longer necessary... how do we unregister?
self.need_cleanup = False
Por último, tenga en cuenta que los manejadores de atexit
no se llama si se " el programa es matado por una señal no manejado por Python, cuando se detecta un error interno fatal Python, o cuando os._exit () es llamado. "
¿Por qué no lo intentas? Sólo me tomó un minuto para comprobar.
(Respuesta: Sí)
Sin embargo, se puede simplificar. no es necesario el cierre.
class Foo:
def cleanup(self):
pass
def start(self):
atexit.register(self.cleanup)
Y para no limpieza dos veces, simplemente marque en el método de limpieza si se necesita una limpieza o no antes de limpiar.