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
24
demandé sur satoru 2014-03-07 12:24:21

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 instruction try ... finally) ou d'appeler exc_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.

24
répondu Martijn Pieters 2014-03-08 10:35:07

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.

1
répondu satoru 2014-03-08 10:42:14