python: les fermetures et les classes
Question
je dois enregistrer une fonction atexit
pour une utilisation avec une classe (voir Foo
ci-dessous pour un exemple) que, malheureusement, je n'ai aucun moyen direct de nettoyage au moyen d'un appel de méthode: autre code, que je n'ai pas le contrôle plus, les appels Foo.start()
et Foo.end()
mais parfois ne pas appeler Foo.end()
si elle rencontre une erreur, alors je dois nettoyer moi-même.
Je pourrais utiliser dans ce contexte, quelques conseils sur les fermetures:
class Foo:
def cleanup(self):
# do something here
def start(self):
def do_cleanup():
self.cleanup()
atexit.register(do_cleanup)
def end(self):
# cleanup is no longer necessary... how do we unregister?
-
Est-ce que le travail de fermeture correctement, par exemple en
do_cleanup
, est la valeur de soi liée correctement? -
Comment puis-je désinscription une routine atexit ()?
-
Y at-il une meilleure façon de le faire?
modifier : c'est Python 2.6.5
La solution
Faire un registre un registre global et une fonction qui appelle une fonction, et les enlever de là en cas de besoin.
cleaners = set()
def _call_cleaners():
for cleaner in list(cleaners):
cleaner()
atexit.register(_call_cleaners)
class Foo(object):
def cleanup(self):
if self.cleaned:
raise RuntimeError("ALREADY CLEANED")
self.cleaned = True
def start(self):
self.cleaned = False
cleaners.add(self.cleanup)
def end(self):
self.cleanup()
cleaners.remove(self.cleanup)
Autres conseils
self
est correctement lié à l'intérieur du rappel à do_cleanup, mais en fait, si tout ce que vous faites est d'appeler la méthode vous pourriez aussi bien utiliser la méthode liée directement.
Vous utilisez atexit.unregister()
pour supprimer le rappel, mais il y a un hic ici que vous devez désenregistrer la même fonction que vous avez enregistré et que vous avez utilisé une fonction imbriquée qui signifie que vous devez stocker une référence à cette fonction. Si vous suivez ma suggestion d'utiliser une méthode liée, vous devez toujours enregistrer une référence à elle:
class Foo:
def cleanup(self):
# do something here
def start(self):
self._cleanup = self.cleanup # Need to save the bound method for unregister
atexit.register(self._cleanup)
def end(self):
atexit.unregister(self._cleanup)
Notez qu'il est toujours possible pour votre code pour quitter sans appeler utres atexit
fonctions enregistrées, par exemple, si le processus est interrompu avec ctrl + break sur les fenêtres ou tué avec SIGABRT sur linux.
En outre comme une autre réponse que vous pouvez simplement utiliser suggère __del__
mais qui peut être problématique pour le nettoyage pendant qu'un programme est sortie car il ne peut être appelé qu'après d'autres GLOBALS dont il a besoin d'accès ont été supprimés.
Sous la direction de noter que lorsque je l'ai écrit cette réponse la question n'a pas précisé Python 2.x. Eh bien, je vais laisser la réponse ici de toute façon dans le cas où il aide quelqu'un d'autre.
Depuis cambrure supprimé son affectation, je parlerai en faveur de nouveau __del__
:
import atexit, weakref
class Handler:
def __init__(self, obj):
self.obj = weakref.ref(obj)
def cleanup(self):
if self.obj is not None:
obj = self.obj()
if obj is not None:
obj.cleanup()
class Foo:
def __init__(self):
self.start()
def cleanup(self):
print "cleanup"
self.cleanup_handler = None
def start(self):
self.cleanup_handler = Handler(self)
atexit.register(self.cleanup_handler.cleanup)
def end(self):
if self.cleanup_handler is None:
return
self.cleanup_handler.obj = None
self.cleanup()
def __del__(self):
self.end()
a1=Foo()
a1.end()
a1=Foo()
a2=Foo()
del a2
a3=Foo()
a3.m=a3
prend en charge les cas suivants: les
- objets où .end est appelé régulièrement; nettoyage tout de suite
- Les objets qui sont libérés sans .end appelé; lorsque le dernier nettoyage référence disparaît
- objets vivants dans les cycles; atexit nettoyage objets
- qui sont gardés en vie; atexit nettoyage
Notez qu'il est important que le gestionnaire de nettoyage contient une référence faible à l'objet, car il serait autrement garder l'objet vivant.
Modifier : Cycles Foo impliquant ne sera pas car Foo instruments __del__
ramasse-miettes,. Pour permettre le cycle suppression au moment de la collecte des ordures, le nettoyage doit être mis hors du cycle.
class Cleanup:
cleaned = False
def cleanup(self):
if self.cleaned:
return
print "cleanup"
self.cleaned = True
def __del__(self):
self.cleanup()
class Foo:
def __init__(self):...
def start(self):
self.cleaner = Cleanup()
atexit.register(Handler(self).cleanup)
def cleanup(self):
self.cleaner.cleanup()
def end(self):
self.cleanup()
Il est important que l'objet de nettoyage n'a pas de références à foo.
Je pense que le code est bien. Il n'y a pas moyen de désinscription, mais vous pouvez définir un indicateur booléen qui désactiverait le nettoyage:
class Foo:
def __init__(self):
self.need_cleanup = True
def cleanup(self):
# do something here
print 'clean up'
def start(self):
def do_cleanup():
if self.need_cleanup:
self.cleanup()
atexit.register(do_cleanup)
def end(self):
# cleanup is no longer necessary... how do we unregister?
self.need_cleanup = False
Enfin, gardez à l'esprit que les gestionnaires de atexit
ne soit pas appelé si « le programme est tué par un signal non manipulé par Python, lorsqu'une erreur fatale interne python est détectée, ou lorsque os._exit () est appelée. "
Pourquoi ne pas essayer? Il ne m'a fallu une minute pour vérifier.
(Réponse: Oui)
Cependant, vous pouvez simplifier. La fermeture est pas nécessaire.
class Foo:
def cleanup(self):
pass
def start(self):
atexit.register(self.cleanup)
Et pour ne pas le nettoyage deux fois, il suffit de cocher dans la méthode de nettoyage si un nettoyage est nécessaire ou non avant de nettoyer.