← 返回文章流

一个 monkeypatch 引起的循环引用问题

最近社区里有人提了一个PR^1,解决了一个潜在的内存泄漏问题。我认为这个问题在很多场景都容易忽略,所以分享在这里。 上代码 import gc class Foo: def bar(self): print("bar") foo = Foo() print("Before", gc.get_count()) foo.bar = foo.bar print("setattr", gc.get_count()) del foo print("After", gc.get_count()) 运行发现 foo 对象无法被正常回收,造成了内存泄漏。这是因为看似不起眼的一行 foo.bar = foo.bar,实际上创建了一个循环引用。 点解? 因为,foo.bar 这个表达式,实际上执行了 Foo.bar.__get__(foo, Foo),返回了一个绑定了 foo 的方法,此方法中持有 foo 的引用。而 foo.bar = foo.bar,相当于把这个结果缓存在了 foo.bar 属性上。之后的 foo.bar 只是从属性上取值,这样 foo 和该方法互相持有对方的引用,出现了循环引用。 现实中我们不太可能会写出 foo.bar = foo.bar 这样的代码,但是在 monkeypatch 场景下,可能很容易被坑到,比如: import requests resp = requests.get("https://example.com", stream=True) resp._fp = CallbackFileWrapper(resp, callback) ... 上述代码 patch 了 requests 库的 Response 对象,让它在被读取完成中调用我们指定的 callback 函数。这里 CallbackFileWrapper 会持有 resp 的引用。 如何避免? 在上述例子中,我们可以使用 weakref 来避免循环引用: import weakref class CallbackFileWrapper: def __init__(self, resp, callback): self.resp_ref = weakref.ref(resp) self.callback = callback def read(self, size): resp = self.resp_ref() if resp is None: return b"" read_bytes = resp.read(size) if not read_bytes: self.callback(resp)

来自 Frost's Blog作者主页
在原博客阅读全文 (新标签页打开)

以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。

一起聊聊 0

尊重不同观点

还没有评论。欢迎分享第一个想法。

RSS SUBSCRIPTIONS

选择 RSS 订阅

把博客汇的文章添加到你常用的阅读器。

关注文章 RSS 订阅本订阅需要登录后使用