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.
Foo
existantes lors de la sortie de l'interpréteur, la barre est fermée?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?__del__
est appelé, est-il garanti que __init__
a déjà été appelé? qu'en est-il si le __init__
est généré?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é.
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.
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>
__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.__init__()
lève une exception, alors l'objet est supposé incomplet et __del__()
ne sera pas appelé.