我正在阅读the source code of the incoming asyncio
package。请注意,在方法的最后,有一个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
答案 0 :(得分:24)
它只是清除对self
的本地引用,确保如果发生异常,则传递给self._loop.call_exception_handler()
的引用是唯一剩余的引用,并且没有创建循环。
这仍然需要这样,因为异常回溯引用了本地命名空间;当函数退出时, 不 将被清除,因为仍有对当地居民的引用。
sys.exc_info()
function documentation中记录了这一点并带有警告:
警告:将 traceback 返回值分配给处理异常的函数中的局部变量将导致循环引用。这将阻止同一函数中的局部变量或回溯引用的任何内容被垃圾回收。由于大多数函数不需要访问回溯,因此最好的解决方案是使用
exctype, value = sys.exc_info()[:2]
之类的东西来仅提取异常类型和值。如果确实需要回溯,请确保在使用后删除它(最好使用try ... finally
语句)或在本身不处理异常的函数中调用exc_info()
。
因为tulip
处理程序构成了一个基本的框架类,所以代码通过从本地名称空间中删除self
来处理回溯循环引用案例,因为它不能保证_callback
或{{ 1}}函数将清除它们的引用。
在CPython中,对象在引用计数降为0时被销毁,但循环引用(在一个循环中引用自身的一系列对象)将永远不会看到它们的引用计数降为0.垃圾收集器确实试图破坏这样的循环,但它不能总是做到这一点或不够快。明确清除引用可以避免创建周期。
例如,如果存在call_exception_handler
方法,垃圾收集器将不会中断循环,因为它不知道在这种情况下安全地打破循环的顺序。
即使没有__del__
方法(框架类不应该假设不是这种情况),最好不要依赖垃圾收集器最终清除周期。
答案 1 :(得分:1)
请注意,此行由Guido在revision 496中引入。
在此版本中,与_run
对应的功能为run:
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
分配。