And*_*rew 4 python garbage-collection exception reference-counting
我定义了两个简单的 Python 函数,它们接受单个参数、引发异常并处理引发的异常。一个函数在引发/处理之前使用变量来引用异常,另一个函数则不使用:
def refcount_unchanged(x):
try:
raise Exception()
except:
pass
def refcount_increases(x):
e = Exception()
try:
raise e
except:
pass
Run Code Online (Sandbox Code Playgroud)
结果函数之一增加了refcount其输入参数的 pythons,另一个则没有:
import sys
a = []
print(sys.getrefcount(a))
for i in range(3):
refcount_unchanged(a)
print(sys.getrefcount(a))
# prints: 2, 2, 2, 2
b = []
print(sys.getrefcount(b))
for i in range(3):
refcount_increases(b)
print(sys.getrefcount(b))
# prints: 2, 3, 4, 5
Run Code Online (Sandbox Code Playgroud)
谁能解释为什么会发生这种情况?
这是PEP-344__traceback__ (Python 2.5)中引入的异常实例属性中的“异常 -> 回溯 -> 堆栈帧 -> 异常”引用循环的副作用,并在PEP-3110 (Python 3.0)等情况下解决)。refcount_unchanged
在 中refcount_increases,可以通过打印以下内容来观察引用循环:
except:
print(e.__traceback__.tb_frame.f_locals) # {'x': [], 'e': Exception()}
Run Code Online (Sandbox Code Playgroud)
这表明它x也在框架的局部变量中被引用。
当垃圾收集器运行或gc.collect()调用 if 时,引用循环将被解决。
在 中refcount_unchanged,根据 PEP-3110 的语义更改,Python 3 生成额外的字节码来删除目标,从而消除引用循环:
def refcount_unchanged(x):
try:
raise Exception()
except:
pass
Run Code Online (Sandbox Code Playgroud)
被翻译成这样:
def refcount_unchanged(x):
try:
raise Exception()
except Exception as e:
try:
pass
finally:
e = None
del e
Run Code Online (Sandbox Code Playgroud)
refcount_increases虽然没有必要(因为垃圾收集器将完成其工作),但您可以refcount_increases通过手动删除变量引用来执行类似的操作:
def refcount_increases(x):
e = Exception()
try:
raise e
except:
pass
finally: # +
del e # +
Run Code Online (Sandbox Code Playgroud)
或者,您可以覆盖变量引用并让隐式删除起作用:
def refcount_increases(x):
e = Exception()
try:
raise e
# except: # -
except Exception as e: # +
pass
Run Code Online (Sandbox Code Playgroud)
异常e和其他局部变量实际上是由 直接引用的e.__traceback__.tb_frame,大概是在 C 代码中。
这可以通过打印以下内容来观察:
print(sys.getrefcount(b))
print(gc.get_referrers(b)[0]) # <frame at ...>
Run Code Online (Sandbox Code Playgroud)
访问e.__traceback__.tb_frame.f_locals会创建一个缓存在帧上的字典(另一个引用周期),并阻碍上面的主动解决方案。
print(sys.getrefcount(b))
print(gc.get_referrers(b)[0]) # {'x': [], 'e': Exception()}
Run Code Online (Sandbox Code Playgroud)
然而,这个引用周期也将由垃圾收集器处理。