web-dev-qa-db-fra.com

Comment forcer la suppression d'un objet python?

Je suis curieux de connaître les détails de __del__ En python, quand et pourquoi il devrait être utilisé et pour quoi il ne devrait pas être utilisé. J'ai appris à mes dépens que ce n'est pas vraiment ce à quoi on s'attendrait naïvement d'un destructeur, en ce sens que ce n'est pas le contraire de __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 __== '__main__':
    foo = Foo()
    foo.open()
    del foo
    import gc
    gc.collect()

J'ai vu dans la documentation que c'est pas garanti __del__() _ les méthodes sont appelées pour les objets qui existent encore lorsque l'interpréteur quitte.

  1. comment peut-on garantir que pour toutes les instances Foo existantes lors de la sortie de l'interpréteur, la barre est fermée?
  2. dans l'extrait de code ci-dessus, la barre se ferme-t-elle sur del foo ou sur gc.collect()... ou ni? si vous souhaitez un contrôle plus précis de ces détails (par exemple, la barre doit être fermée lorsque l'objet n'est pas référencé), quel est le moyen habituel de le mettre en œuvre?
  3. lorsque __del__ est appelé, est-il garanti que __init__ a déjà été appelé? qu'en est-il si le __init__ est généré?
60
wim

Les gestionnaires de contexte, autrement dit l'instruction with, permettent de fermer des ressources:

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 __== '__main__':
  with Foo() as foo:
    print foo, foo.bar

sortie:

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

2) Les objets Python sont supprimés lorsque leur nombre de références est 0. Dans votre exemple, le del foo supprime la dernière référence pour __del__ s'appelle instantanément. Le GC n’a aucune part dans cela.

class Foo(object):

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

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

sortie:

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

Le gc n'a rien à voir avec la suppression de vos objets et de la plupart des autres objets. Il est là pour nettoyer lorsque le comptage simple de références ne fonctionne pas, à cause des références propres ou des références circulaires:

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 __== '__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"

sortie:

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) permet de voir:

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

        raise Exception

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

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

donne:

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>

Les objets sont créés avec __new__ puis passé à __init__ comme self. Après une exception dans __init__, l’objet n’a généralement pas de nom (c.-à-d. le f = la partie n'est pas exécutée), leur nombre de réf est égal à 0. Cela signifie que l'objet est supprimé normalement et __del__ est appelé.

69
Jochen Ritzel

En général, pour vous assurer que quelque chose se passe quoi qu'il arrive, vous utilisez

from exceptions import NameError

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

Les blocs finally seront exécutés, qu'il y ait ou non une erreur dans le bloc try, et qu'il y ait ou non une erreur dans la gestion des erreurs ayant lieu dans except blocs . Si vous ne gérez pas une exception déclenchée, elle le sera quand même après l'exécution du bloc finally.

La manière générale de s’assurer qu’un fichier est fermé est d’utiliser un "gestionnaire de contexte".

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

with open(x) as f:
    # do stuff

Cela fermera automatiquement f.

Pour votre question n ° 2, bar se ferme immédiatement dès que le nombre de références atteint zéro. Par conséquent, del foo S'il n'y a pas d'autres références.

Les objets ne sont PAS créés par __init__, Ils sont créés par __new__.

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

Lorsque vous effectuez foo = Foo(), il se passe réellement deux choses. Un nouvel objet est en cours de création, __new__, Puis il est initialisé, __init__. Il est donc impossible d'appeler del foo Avant que ces deux étapes ne soient terminées. Cependant, s'il y a une erreur dans __init__, __del__ Sera quand même appelé car l'objet a déjà été créé dans __new__.

Modifier: corrigé lorsque la suppression se produit si un compte de références décroît à zéro.

8
agf

Peut-être cherchez-vous un gestionnaire de contexte ?

>>> 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
John La Rooy
  1. Ajoutez un gestionnaire de sortie qui ferme toutes les barres.
  2. __del__() est appelé lorsque le nombre de références à un objet atteint 0 alors que le VM est toujours en cours d'exécution. Cela peut être causé par le GC.
  3. Si __init__() lève une exception, alors l'objet est supposé incomplet et __del__() ne sera pas appelé.
2