55

Tengo curiosidad acerca de los detalles de __del__ en python, cuándo y por qué se debe utilizar y para qué no se debe usar. He aprendido por las malas que no es realmente lo que se esperaría ingenuamente de un destructor, ya que no es lo opuesto a __new__/__init__.Cómo forzar la eliminación de un objeto python?

class Foo(object): 

    def __init__(self): 
     self.bar = None 

    def open(self): 
     if self.bar != 'open': 
      print 'opening the bar' 
      self.bar = 'open' 

    def close(self): 
     if self.bar != 'closed': 
      print 'closing the bar' 
      self.bar = 'close' 

    def __del__(self): 
     self.close() 

if __name__ == '__main__': 
    foo = Foo() 
    foo.open() 
    del foo 
    import gc 
    gc.collect() 

vi en la documentación que es no garantizados están llamados __del__() métodos para objetos que aún existen al salir del intérprete.

  1. cómo se puede garantizar que para cualquier Foo instancias existentes cuando se cierra la intérprete, el bar está cerrado?
  2. en el fragmento de código anterior, ¿se cierra la barra en del foo o en gc.collect() ... o ninguno de los dos? si desea un control más preciso de esos detalles (por ejemplo, la barra debe cerrarse cuando el objeto no está referenciado), ¿cuál es la forma habitual de implementar eso?
  3. cuando se llama __del__ ¿está garantizado que __init__ ya se ha llamado? ¿Qué pasa si el __init__ se plantea?

Respuesta

64

La manera de cerrar los recursos son gestores de contexto, también conocido como la declaración with:

class Foo(object): 

    def __init__(self): 
    self.bar = None 

    def __enter__(self): 
    if self.bar != 'open': 
     print 'opening the bar' 
     self.bar = 'open' 
    return self # this is bound to the `as` part 

    def close(self): 
    if self.bar != 'closed': 
     print 'closing the bar' 
     self.bar = 'close' 

    def __exit__(self, *err): 
    self.close() 

if __name__ == '__main__': 
    with Foo() as foo: 
    print foo, foo.bar 

de salida:

opening the bar 
<__main__.Foo object at 0x17079d0> open 
closing the bar 

2) objetos de Python se eliminan cuando su recuento de referencia es 0. En su ejemplo, el del foo elimina la última referencia así que __del__ se llama al instante. El GC no tiene parte en esto.

class Foo(object): 

    def __del__(self): 
     print "deling", self 

if __name__ == '__main__': 
    import gc 
    gc.disable() # no gc 
    f = Foo() 
    print "before" 
    del f # f gets deleted right away 
    print "after" 

de salida:

before 
deling <__main__.Foo object at 0xc49690> 
after 

El gc no tiene nada que ver con la eliminación de la mayoría de sus y otros objetos. Está ahí para limpiar cuando el conteo simple remisión no funciona, debido a la auto-referencias o referencias circulares:

class Foo(object): 
    def __init__(self, other=None): 
     # make a circular reference 
     self.link = other 
     if other is not None: 
      other.link = self 

    def __del__(self): 
     print "deling", self 

if __name__ == '__main__': 
    import gc 
    gc.disable() 
    f = Foo(Foo()) 
    print "before" 
    del f # nothing gets deleted here 
    print "after" 
    gc.collect() 
    print gc.garbage # The GC knows the two Foos are garbage, but won't delete 
        # them because they have a __del__ method 
    print "after gc" 
    # break up the cycle and delete the reference from gc.garbage 
    del gc.garbage[0].link, gc.garbage[:] 
    print "done" 

de salida:

before 
after 
[<__main__.Foo object at 0x22ed8d0>, <__main__.Foo object at 0x22ed950>] 
after gc 
deling <__main__.Foo object at 0x22ed950> 
deling <__main__.Foo object at 0x22ed8d0> 
done 

3) Vamos a ver:

class Foo(object): 
    def __init__(self): 

     raise Exception 

    def __del__(self): 
     print "deling", self 

if __name__ == '__main__': 
    f = Foo() 

gives:

Traceback (most recent call last): 
    File "asd.py", line 10, in <module> 
    f = Foo() 
    File "asd.py", line 4, in __init__ 
    raise Exception 
Exception 
deling <__main__.Foo object at 0xa3a910> 

Los objetos se crean con __new__ y luego pasan a __init__ como self. Después de una excepción en __init__, el objeto normalmente no tendrá un nombre (es decir, la parte f = no se ejecuta) por lo que su recuento de referencias es 0. Esto significa que el objeto se elimina normalmente y se llama al __del__.

+0

> La forma de cerrar recursos son los gestores de contexto, también conocido como la declaración con. Excelente consejo. No sabía que 'con' podría usarse para contener el alcance de cualquier objeto de esta manera. –

+3

Esta respuesta es muy esclarecedora. ¡Gracias por la explicación clara! –

2
  1. Añadir un exit handler que cierra todos los bares.
  2. __del__() se invoca cuando el número de referencias a un objeto llega a 0 mientras la máquina virtual todavía se está ejecutando. Esto puede ser causado por el GC.
  3. Si __init__() lanza una excepción, entonces se supone que el objeto está incompleto y no se invocará __del__().
+2

Como nota al margen: os._exit permite el desvío completo de todas las funciones de manejo cercanas. – cwallenpoole

8

En general, para asegurarse de que algo sucede no importa qué, se utiliza

from exceptions import NameError 

try: 
    f = open(x) 
except ErrorType as e: 
    pass # handle the error 
finally: 
    try: 
     f.close() 
    except NameError: pass 

finally bloques se ejecutará si hay un error en el bloque try, y si hay o no un error en el manejo de errores que tiene lugar en los bloques except. Si no maneja una excepción que se genera, aún se generará después de que se haya ejecutado el bloque finally.

La forma general de asegurarse de que un archivo está cerrado es utilizar un "administrador de contexto".

http://docs.python.org/reference/datamodel.html#context-managers

with open(x) as f: 
    # do stuff 

Esto se cerrará automáticamente f.

Para su pregunta n. ° 2, bar se cierra inmediatamente cuando su recuento de referencias llega a cero, por lo que en del foo si no hay otras referencias.

Los objetos NO están creados por __init__, son creados por __new__.

http://docs.python.org/reference/datamodel.html#object.new

Al hacer foo = Foo() dos cosas están ocurriendo en realidad, primero se crea un nuevo objeto, __new__, entonces se está inicializando, __init__. Por lo tanto, no hay forma de que pueda llamar al del foo antes de que ambos pasos hayan tenido lugar. Sin embargo, si hay un error en __init__, se llamará a __del__ porque el objeto ya se creó en __new__.

Editar: Se corrige cuando se produce una eliminación si el recuento de referencias disminuye a cero.

+1

Su ejemplo 'try..except..finally' está roto: si' open() 'arroja una excepción' f' se desactivará y finalmente no funcionará. – Duncan

+0

Gracias, corregido. Sin embargo, aún se aseguraría de que 'f' se cerró, ya que el error solo se produjo en el caso de que nunca se abriera. – agf

+2

@afg está equivocado sobre el gc y cuando se eliminan los objetos, vea mi respuesta. –

5

Quizás buscas un context manager?

>>> class Foo(object): 
... def __init__(self): 
...  self.bar = None 
... def __enter__(self): 
...  if self.bar != 'open': 
...  print 'opening the bar' 
...  self.bar = 'open' 
... def __exit__(self, type_, value, traceback): 
...  if self.bar != 'closed': 
...  print 'closing the bar', type_, value, traceback 
...  self.bar = 'close' 
... 
>>> 
>>> with Foo() as f: 
...  # oh no something crashes the program 
...  sys.exit(0) 
... 
opening the bar 
closing the bar <type 'exceptions.SystemExit'> 0 <traceback object at 0xb7720cfc> 
Cuestiones relacionadas