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

¿Fue útil?

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top