27

我正在阅读传入的源代码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
4

2 回答 2

27

它只是清除对 的本地引用self,确保如果发生异常,传递给的引用self._loop.call_exception_handler()是唯一剩余的引用,并且没有创建循环。

这里仍然需要这个,因为异常回溯引用了本地命名空间;当函数退出时不会被清除,因为仍然存在对当地人的引用。

这记录在sys.exc_info()功能文档中并带有警告:

警告:将回溯返回值分配给正在处理异常的函数中的局部变量将导致循环引用。这将防止同一函数中的局部变量或回溯引用的任何内容被垃圾收集。由于大多数函数不需要访问回溯,最好的解决方案是使用类似的东西exctype, value = sys.exc_info()[:2]只提取异常类型和值。如果您确实需要回溯,请确保在使用后将其删除(最好使用try ... finally语句完成)或调用exc_info()本身不处理异常的函数。

因为tulip处理程序形成了一个基本的框架类,所以代码通过从本地命名空间中删除来处理回溯循环引用案例self,因为它不能保证_callbackorcall_exception_handler函数将清除它们的引用。

在 CPython 中,当对象的引用计数降至 0 时,对象将被销毁,但循环引用(在一个循环中引用自身的一系列对象)永远不会看到它们的引用计数降至 0。垃圾收集器确实试图打破这样的循环,但它不能总是这样做或不够快。显式清除引用可避免创建循环。

例如,如果有一个__del__方法,垃圾收集器不会中断一个循环,因为它不知道在这种情况下以什么顺序安全地中断一个循环。

即使没有__del__方法(框架类永远不应该假设不会是这种情况),最好不要依赖垃圾收集器最终清除周期。

于 2014-03-07T08:27:45.050 回答
1

请注意,此行是Guido在修订版 496中引入的。

在这个版本中,对应的函数_runrun

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()in的结果,但记录对象在调用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这里没有必要分配。

于 2014-03-08T10:00:26.103 回答