self = None是做什么的?

时间:2014-03-07 08:24:21

标签: python garbage-collection python-internals python-asyncio

我正在阅读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

2 个答案:

答案 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.exceptionsys.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分配。