Что делает self = None?

Я читаю исходный код входящие asyncio пакета. Обратите внимание, что в конце метода есть self = None заявление. Что он делает?

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.

Я думал, что он сотрет экземпляр, но следующий тест не предлагает этого:

class K:
    def haha(self):
        self = None

a = K()
a.haha()
print(a) # a is still an instance

2 ответов


он просто очищает локальную ссылку на self, убедившись, что при возникновении исключения ссылка передается в self._loop.call_exception_handler() - единственная оставшаяся ссылка, и цикл не был создан.

это все еще необходимо здесь, потому что локальное пространство имен ссылается на трассировку исключений; это будет не будет очищен, когда функция выйдет, поскольку есть ссылка на местных жителей, все еще живых.

это описано в sys.exc_info() документация по функциям предупреждение:

предупреждение: назначение traceback возвращаемое значение локальной переменной в функции, обрабатывающей исключение, вызовет циклическую ссылку. Это предотвратит сбор мусора, на который ссылается локальная переменная в той же функции или трассировка. Поскольку большинству функций не требуется доступ к обратной трассировке, лучшим решением является использование что-то вроде exctype, value = sys.exc_info()[:2] для извлечения только типа исключения и значения. Если вам нужна обратная трассировка, обязательно удалите ее после использования (лучше всего сделать с try ... finally заявление) или позвонить exc_info() в функции, которая сама не обрабатывает исключение.

, потому что tulip обработчики формируют фундаментальный класс фреймворка код обрабатывает циклический ссылочный случай трассировки путем удаления self из локального пространства имен, поскольку он не может гарантировать, что _callback или call_exception_handler функции очистят свои ссылки.

в CPython объекты уничтожаются, когда их счетчик ссылок падает до 0, но циклическая ссылка (серия объектов, ссылающихся на себя в цикле) никогда не увидит, что их счетчик ссылок падает до 0. Сборщик мусора пытается разорвать такие циклы, но он не всегда может сделать это или недостаточно быстро. Явная очистка ссылок позволяет избежать создания циклов.

например, если есть __del__ метод, сборщик мусора не разорвет цикл, так как он не будет знать, в каком порядке безопасно разорвать цикл в этом случае.

даже если нет __del__ метод(который класс framework никогда не должен предполагать, что это не так) лучше не полагаться на циклы очистки сборщика мусора.


обратите внимание, что эта строка введена в пересмотр 496 Гвидо.

в этой редакции функция, которая соответствовала _run is выполнить:

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 это просто обычный регистратор:logging.getLogger("tulip").

под капотом Logger.exception сохраняет результат sys.exc_info() на LogRecord, но объект записи не сохраняется после exception звонок.

чтобы проверить, что logging.exception не вызывает ссылочный цикл, я сделал следующий эксперимент:

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

вот результат:

$ 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

объект t мусор собирается, как и ожидалось.

Итак, я не думаю, что self = None назначение здесь.