Что делает 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
назначение здесь.