Que fait self = None?
Je lis le code source du paquet asyncio
entrant . Notez qu'à la fin de la méthode, il y a une instruction self = None
. Que faut-il faire?
def _run(self):
try:
self._callback(*self._args)
except Exception as exc:
msg = 'Exception in callback {}{!r}'.format(self._callback,
self._args)
self._loop.call_exception_handler({
'message': msg,
'exception': exc,
'handle': self,
})
self = None # Needed to break cycles when an exception occurs.
Je pensais que cela effacerait l'instance, mais le test suivant Ne le suggère pas:
class K:
def haha(self):
self = None
a = K()
a.haha()
print(a) # a is still an instance
2 réponses
Il efface simplement la référence locale à self
, en s'assurant que si une exception se produit, la référence passée à self._loop.call_exception_handler()
est la seule référence restante et qu'aucun cycle n'a été créé.
Ceci est toujours nécessaire ici car l'espace de noms local est référencé par l'exception traceback; il Non être effacé lorsque la fonction se termine car il y a une référence aux locaux vivants encore.
Ceci est documenté dans le sys.exc_info()
la fonction documentation, avec un avertissement:
Avertissement : L'affectation de la valeur de retourtraceback à une variable locale dans une fonction qui gère une exception entraînera une référence circulaire. Cela empêchera tout ce qui est référencé par une variable locale dans la même fonction ou par le traceback d'être collecté. Comme la plupart des fonctions n'ont pas besoin d'accéder au traceback, la meilleure solution consiste à utiliser quelque chose comme
exctype, value = sys.exc_info()[:2]
pour extraire uniquement le type d'exception et valeur. Si vous avez besoin du traceback, assurez-vous de le supprimer après utilisation (mieux fait avec une instructiontry ... finally
) ou d'appelerexc_info()
dans une fonction qui ne gère pas elle-même une exception.
Étant donné que les gestionnaires tulip
forment une classe de framework fondamentale, le code gère le cas de référence circulaire traceback en supprimant self
de l'espace de noms local, car il ne peut pas garantir que les fonctions _callback
ou call_exception_handler
effaceront leurs références.
Dans CPython, les objets sont détruits lorsque leur nombre de références tombe à 0, mais une référence cyclique (une série d'objets se référençant dans un cycle) ne verra jamais leur nombre de références tomber à 0. Le garbage collector essaie de briser de tels cycles mais il ne peut pas toujours le faire ou pas assez vite. Effacer explicitement les références évite de créer des cycles.
Par exemple, s'il existe une méthode __del__
, le garbage collector ne cassera pas un cycle car il ne saura pas dans quel ordre casser un cycle en toute sécurité cas.
Même s'il n'y a pas de méthode __del__
(qu'une classe framework ne devrait jamais supposer ne sera pas le cas), il est préférable de ne pas compter sur le garbage collector pour finalement Effacer les cycles.
Notez que cette ligne est introduit dans révision 496 par Guido.
Lors de cette révision, la fonction qui correspond à _run
est exécuter:
def run(self):
try:
self._callback(*self._args)
except Exception:
tulip_log.exception('Exception in callback %s %r',
self._callback, self._args)
self = None # Needed to break cycles when an exception occurs.
tulip_log
est un enregistreur: logging.getLogger("tulip")
.
Sous le capot, Logger.exception
stocke le résultat de sys.exc_info()
dans LogRecord
, mais l'objet d'enregistrement ne persistent après la exception
appeler.
Pour vérifier que logging.exception
ne provoque pas de cycle de référence, j'ai fait l'expérience suivante:
import time
import logging
class T:
def __del__(self):
print('T.__del__ called')
def test(self):
try:
1 / 0
except Exception:
logging.exception("Testing")
def run():
t = T()
t.test()
# t is supposed to be garbaged collected
run()
time.sleep(10) # to emulate a long running process
C'est le résultat:
$ python test.py
ERROR:root:Testing
Traceback (most recent call last):
File "test.py", line 11, in test
1 / 0
ZeroDivisionError: integer division or modulo by zero
T.__del__ called
L'objet t
garbage collecté comme prévu.
Donc, je ne pense pas que l'affectation de self = None
soit nécessaire ici.