为什么为 Python 函数中引发的异常创建变量名会影响该函数的输入变量的引用计数?
Why does creating a variable name for an exception raised in a Python function affect the reference count of an input variable to that function?
我定义了两个简单的 Python 函数,它们接受一个参数,引发异常,并处理引发的异常。一个函数使用变量引用raising/handling之前的异常,另一个没有:
def refcount_unchanged(x):
try:
raise Exception()
except:
pass
def refcount_increases(x):
e = Exception()
try:
raise e
except:
pass
其中一个结果函数为其输入参数增加了 pythons refcount
,另一个则没有:
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
谁能解释一下为什么会这样?
好像把题目写出来了helped us realize part of the answer。如果我们在每次调用 refcount_increases
后 garbage-collect,refcount 不再增加。有趣的!我认为这不是我们问题的完整答案,但它肯定具有启发性。欢迎任何进一步的信息。
import gc
c = []
print(sys.getrefcount(c))
for i in range(3):
refcount_increases(c)
gc.collect()
print(sys.getrefcount(c))
# prints: 2, 2, 2, 2
这是 PEP-344 (Python 2.5), and resolved in cases like refcount_unchanged
in PEP-3110 中引入的异常实例上 __traceback__
属性的“异常 -> 回溯 -> 堆栈框架 -> 异常”引用循环的副作用(Python 3.0).
在refcount_increases
中,可以通过打印这个来观察引用周期:
except:
print(e.__traceback__.tb_frame.f_locals) # {'x': [], 'e': Exception()}
这表明 x
也在框架的局部变量中被引用。
垃圾收集器运行时或调用 gc.collect()
时解决引用循环问题。
在refcount_unchanged
中,根据PEP-3110的Semantic Changes,Python 3生成额外的字节码来删除目标,从而消除循环引用:
def refcount_unchanged(x):
try:
raise Exception()
except:
pass
被翻译成类似的东西:
def refcount_unchanged(x):
try:
raise Exception()
except Exception as e:
try:
pass
finally:
e = None
del e
正在解决refcount_increases
中的引用循环
虽然没有必要(因为垃圾收集器会完成它的工作),但您可以在 refcount_increases
中通过手动删除变量引用来做类似的事情:
def refcount_increases(x):
e = Exception()
try:
raise e
except:
pass
finally: # +
del e # +
或者,您可以覆盖变量引用并让隐式删除起作用:
def refcount_increases(x):
e = Exception()
try:
raise e
# except: # -
except Exception as e: # +
pass
关于引用循环的更多信息
异常e
和其他局部变量实际上被e.__traceback__.tb_frame
直接引用,大概是在C代码中。
这可以通过打印来观察:
print(sys.getrefcount(b))
print(gc.get_referrers(b)[0]) # <frame at ...>
访问 e.__traceback__.tb_frame.f_locals
会创建缓存在框架上的字典(另一个引用周期)并阻碍上述主动解决方案。
print(sys.getrefcount(b))
print(gc.get_referrers(b)[0]) # {'x': [], 'e': Exception()}
但是,这个引用循环也将由垃圾收集器处理。
我定义了两个简单的 Python 函数,它们接受一个参数,引发异常,并处理引发的异常。一个函数使用变量引用raising/handling之前的异常,另一个没有:
def refcount_unchanged(x):
try:
raise Exception()
except:
pass
def refcount_increases(x):
e = Exception()
try:
raise e
except:
pass
其中一个结果函数为其输入参数增加了 pythons refcount
,另一个则没有:
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
谁能解释一下为什么会这样?
好像把题目写出来了helped us realize part of the answer。如果我们在每次调用 refcount_increases
后 garbage-collect,refcount 不再增加。有趣的!我认为这不是我们问题的完整答案,但它肯定具有启发性。欢迎任何进一步的信息。
import gc
c = []
print(sys.getrefcount(c))
for i in range(3):
refcount_increases(c)
gc.collect()
print(sys.getrefcount(c))
# prints: 2, 2, 2, 2
这是 PEP-344 (Python 2.5), and resolved in cases like refcount_unchanged
in PEP-3110 中引入的异常实例上 __traceback__
属性的“异常 -> 回溯 -> 堆栈框架 -> 异常”引用循环的副作用(Python 3.0).
在refcount_increases
中,可以通过打印这个来观察引用周期:
except:
print(e.__traceback__.tb_frame.f_locals) # {'x': [], 'e': Exception()}
这表明 x
也在框架的局部变量中被引用。
垃圾收集器运行时或调用 gc.collect()
时解决引用循环问题。
在refcount_unchanged
中,根据PEP-3110的Semantic Changes,Python 3生成额外的字节码来删除目标,从而消除循环引用:
def refcount_unchanged(x):
try:
raise Exception()
except:
pass
被翻译成类似的东西:
def refcount_unchanged(x):
try:
raise Exception()
except Exception as e:
try:
pass
finally:
e = None
del e
正在解决refcount_increases
中的引用循环
虽然没有必要(因为垃圾收集器会完成它的工作),但您可以在 refcount_increases
中通过手动删除变量引用来做类似的事情:
def refcount_increases(x):
e = Exception()
try:
raise e
except:
pass
finally: # +
del e # +
或者,您可以覆盖变量引用并让隐式删除起作用:
def refcount_increases(x):
e = Exception()
try:
raise e
# except: # -
except Exception as e: # +
pass
关于引用循环的更多信息
异常e
和其他局部变量实际上被e.__traceback__.tb_frame
直接引用,大概是在C代码中。
这可以通过打印来观察:
print(sys.getrefcount(b))
print(gc.get_referrers(b)[0]) # <frame at ...>
访问 e.__traceback__.tb_frame.f_locals
会创建缓存在框架上的字典(另一个引用周期)并阻碍上述主动解决方案。
print(sys.getrefcount(b))
print(gc.get_referrers(b)[0]) # {'x': [], 'e': Exception()}
但是,这个引用循环也将由垃圾收集器处理。