class Package:
def __init__(self):
self.files = []
# ...
def __del__(self):
for file in self.files:
os.unlink(file)
__del__(self)
ci-dessus échoue avec une exception AttributeError. Je comprends Python ne garantit pas l'existence de "variables globales" (données de membre dans ce contexte?) Lorsque __del__()
est appelé. Si tel est le cas et que cela constitue la raison de l'exception, comment puis-je m'assurer que l'objet se détruit correctement?
Je vous recommande d'utiliser l'instruction with
de Python pour gérer les ressources à nettoyer. Le problème avec l'utilisation d'une instruction close()
explicite est que vous devez vous inquiéter des personnes qui oublient de l'appeler ou de la placer dans un bloc finally
afin d'éviter une fuite de ressource lorsqu'une exception se produit.
Pour utiliser l'instruction with
, créez une classe avec les méthodes suivantes:
def __enter__(self)
def __exit__(self, exc_type, exc_value, traceback)
Dans votre exemple ci-dessus, vous utiliseriez
class Package:
def __init__(self):
self.files = []
def __enter__(self):
return self
# ...
def __exit__(self, exc_type, exc_value, traceback):
for file in self.files:
os.unlink(file)
Ensuite, lorsque quelqu'un souhaite utiliser votre classe, il procède comme suit:
with Package() as package_obj:
# use package_obj
La variable package_obj sera une instance de type Package (c'est la valeur renvoyée par la méthode __enter__
). Sa méthode __exit__
sera automatiquement appelée, qu’une exception se produise ou non.
Vous pouvez même pousser cette approche un peu plus loin. Dans l'exemple ci-dessus, quelqu'un pouvait toujours instancier Package en utilisant son constructeur sans utiliser la clause with
. Vous ne voulez pas que cela se produise. Vous pouvez résoudre ce problème en créant une classe PackageResource qui définit les méthodes __enter__
et __exit__
. Ensuite, la classe Package sera définie strictement dans la méthode __enter__
et retournée. De cette manière, l'appelant ne pourrait jamais instancier la classe Package sans utiliser l'instruction with
:
class PackageResource:
def __enter__(self):
class Package:
...
self.package_obj = Package()
return self.package_obj
def __exit__(self, exc_type, exc_value, traceback):
self.package_obj.cleanup()
Vous utiliseriez ceci comme suit:
with PackageResource() as package_obj:
# use package_obj
La méthode standard consiste à utiliser atexit.register
:
_# package.py
import atexit
import os
class Package:
def __init__(self):
self.files = []
atexit.register(self.cleanup)
def cleanup(self):
print("Running cleanup...")
for file in self.files:
print("Unlinking file: {}".format(file))
# os.unlink(file)
_
Cependant, gardez à l'esprit que cela persistera toutes les instances créées de Package
jusqu'à ce que Python soit terminé.
Démo utilisant le code ci-dessus enregistré en tant que package.py :
_$ python
>>> from package import *
>>> p = Package()
>>> q = Package()
>>> q.files = ['a', 'b', 'c']
>>> quit()
Running cleanup...
Unlinking file: a
Unlinking file: b
Unlinking file: c
Running cleanup...
_
En annexe de réponse de Clint , vous pouvez simplifier PackageResource
avec contextlib.contextmanager
:
_@contextlib.contextmanager
def packageResource():
class Package:
...
package = Package()
yield package
package.cleanup()
_
Sinon, bien que probablement pas en Pythonic, vous pouvez remplacer _Package.__new__
_:
_class Package(object):
def __new__(cls, *args, **kwargs):
@contextlib.contextmanager
def packageResource():
# adapt arguments if superclass takes some!
package = super(Package, cls).__new__(cls)
package.__init__(*args, **kwargs)
yield package
package.cleanup()
def __init__(self, *args, **kwargs):
...
_
et utilisez simplement with Package(...) as package
.
Pour raccourcir les choses, nommez votre fonction de nettoyage close
et utilisez contextlib.closing
, auquel cas vous pouvez utiliser la classe non modifiée Package
via with contextlib.closing(Package(...))
. ou substituer son ___new__
_ au plus simple
_class Package(object):
def __new__(cls, *args, **kwargs):
package = super(Package, cls).__new__(cls)
package.__init__(*args, **kwargs)
return contextlib.closing(package)
_
Et ce constructeur est hérité, vous pouvez donc simplement hériter, par exemple.
_class SubPackage(Package):
def close(self):
pass
_
Je ne pense pas qu'il soit possible, par exemple, de supprimer des membres avant que __del__
soit appelé. Je suppose que la raison de votre AttributeError particulier est ailleurs (vous supprimez peut-être par erreur self.file ailleurs).
Cependant, comme les autres l'ont fait remarquer, vous devriez éviter d'utiliser __del__
. La raison principale en est que les instances avec __del__
ne seront pas récupérées (elles ne seront libérées que lorsque leur nombre de références atteindra 0). Par conséquent, si vos instances sont impliquées dans des références circulaires, elles vivront en mémoire aussi longtemps que l'application s'exécutera. (Je me trompe peut-être à propos de tout cela, il faudrait que je relise les documents gc, mais je suis plutôt sûr que cela fonctionne comme ça).
Je pense que le problème pourrait être dans __init__
s'il y a plus de code que celui indiqué?
__del__
sera appelé même si __init__
n'a pas été exécuté correctement ou a généré une exception.
Une meilleure alternative consiste à utiliser faibleref.finalize . Voir les exemples sur Finalizer Objects et Comparaison des finaliseurs avec les méthodes __del __ () .
Enveloppez simplement votre destructeur avec une instruction try/except et il ne lèvera pas une exception si vos globals sont déjà éliminés.
Éditer
Essaye ça:
from weakref import proxy
class MyList(list): pass
class Package:
def __init__(self):
self.__del__.im_func.files = MyList([1,2,3,4])
self.files = proxy(self.__del__.im_func.files)
def __del__(self):
print self.__del__.im_func.files
Il va farcir la liste de fichiers dans la fonction del dont l'existence est garantie au moment de l'appel. Le proxy faible vise à empêcher Python, ou vous-même, de supprimer la variable self.files (si elle est supprimée, la liste de fichiers d'origine ne sera pas affectée). Si ce n'est pas le cas, cela est en train d'être supprimé alors qu'il y a plus de références à la variable, vous pouvez alors supprimer l'encapsulation de proxy.
Voici un squelette de travail minimal:
class SkeletonFixture:
def __init__(self):
pass
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def method(self):
pass
with SkeletonFixture() as fixture:
fixture.method()
Important: retourne soi-même
Si vous êtes comme moi et que vous oubliez la partie return self
(de réponse correcte de Clint Miller ), vous regarderez ce non-sens:
Traceback (most recent call last):
File "tests/simplestpossible.py", line 17, in <module>
fixture.method()
AttributeError: 'NoneType' object has no attribute 'method'
J'ai passé une demi-journée à ce sujet. J'espère que ça aide la prochaine personne.
Il semble que la façon idiomatique de faire cela est de fournir une méthode close()
(ou similaire) et de l'appeler explicitement.