【问题标题】:What does self = None do?self = None 有什么作用?
【发布时间】:2014-03-07 08:24:21
【问题描述】:

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

【问题讨论】:

  • 也许问题应该是“为什么将self 设置为None 会中断循环?什么循环?”

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


【解决方案1】:

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

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

这在sys.exc_info() function documentation 中记录了一个警告:

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

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

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

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

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

【讨论】:

  • 你能举一个这样的循环引用的例子吗?
  • 我还是不明白...我的意思是,如果语句self = None被命中,那么显然会命中方法的结尾,在这种情况下本地引用将丢失无论如何,我们为什么需要self = None
  • @msvalkon:任何直接或间接引用对象的东西都可以。 self.ref = self 是最简单的例子。
  • @MartijnPieters 我不明白,是不是还有一个圆圈,即。 self->_loop->self,我们从locals()中删除对self的引用后的事件?
  • @satoru:也许,但至少 self->call_exception_handler->exc->__traceback__->f_locals->self 循环消失了。
【解决方案2】:

请注意,此行是 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

【讨论】:

  • 同一个提交在多个位置添加了self = None,因为框架这样做是一种很好的做法,而不是因为代码库单独表现出循环引用。
  • @MartijnPieters 您能否给我看一个生成循环引用的示例?可能是_callback
  • 我还没有研究过tulip/asyncio库;我不知道设计意图,也不知道您研究的提交是否正在进行中(例如,即使在当时还不是完整的库)。 logging 库允许您注册自定义处理程序和格式化程序;不要只使用那里的默认代码库。不幸的是,我现在没有时间构建示例案例 ATM(此评论来自通过 3G 连接的智能手机)。
猜你喜欢
  • 2016-11-12
  • 2021-03-31
  • 2021-03-04
  • 2020-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多