一个 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)
以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。
登录后,加入这场讨论。
登录参与