Pregunta ¿Cómo forzar la eliminación de un objeto python?


Tengo curiosidad sobre los detalles de __del__ en python, cuándo y por qué debería usarse y para qué no se debería 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__.

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 garantizado __del__() Se requieren métodos para objetos que aún existen cuando el intérprete sale.

  1. cómo se puede garantizar que para cualquier Foo instancias existentes cuando el intérprete sale, la barra está cerrada?
  2. en el fragmento de código de arriba se cierra la barra del foo o en gc.collect()... ¿o ninguno? 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 __del__ se llama se garantiza que __init__ ya ha sido llamado? ¿Qué pasa si el __init__ ¿elevado?

59
2017-07-21 07:09


origen


Respuestas:


La forma de cerrar los recursos son los administradores de contexto, también conocido como el with declaración:

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

salida:

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

2) Los objetos de Python se eliminan cuando su recuento de referencias es 0. En su ejemplo, del foo elimina la última referencia para __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"

salida:

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

los gc no tiene nada que ver con eliminar tu y la mayoría de otros objetos. Está ahí para limpiar cuando el recuento de referencia simple no funciona, debido a referencias propias 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"

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) Veamos:

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

        raise Exception

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

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

da:

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__ luego pasó a __init__ como self. Después de una excepción en __init__, el objeto normalmente no tendrá un nombre (es decir, f = parte no se ejecuta) por lo que su recuento de ref es 0. Esto significa que el objeto se elimina normalmente y __del__ se llama.


67
2017-07-21 07:55



En general, para asegurarse de que ocurra algo sin importar qué, use

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án si hay o no un error en el try bloquear, y si hay o no un error en el manejo de errores que tiene lugar en except bloques. Si no maneja una excepción que se plantea, aún se levantará después de la finally bloque está ejecutado.

La forma general de asegurarse de que un archivo esté cerrado es usar 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 el recuento de referencia llega a cero, por lo tanto del foo si no hay otras referencias

Los objetos NO son creados por __init__, son creados por __new__.

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

Cuando tu lo hagas foo = Foo() dos cosas están sucediendo realmente, primero se está creando un nuevo objeto, __new__, entonces se está inicializando, __init__. Así que no hay forma de que puedas llamar del foo antes de que ambos pasos hayan tenido lugar. Sin embargo, si hay un error en __init__, __del__todavía se llamará porque el objeto ya fue creado en __new__.

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


8
2017-07-21 07:21



Quizás estás buscando un administrador de contexto?

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

5
2017-07-21 07:21



  1. Añadir un manejador de salida que cierra todos los bares.
  2. __del__() se llama cuando el número de referencias a un objeto llega a 0 mientras la máquina virtual todavía está ejecutándose. Esto puede ser causado por el GC.
  3. Si __init__() plantea una excepción, entonces se supone que el objeto está incompleto y __del__() no será invocado

2
2017-07-21 07:17