为什么为 Python 函数中引发的异常创建变量名会影响该函数的输入变量的引用计数?
我定义了两个简单的 Python 函数,它们接受单个参数、引发异常并处理引发的异常。一个函数在引发/处理之前使用变量来引用异常,另一个函数则不然:
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
任何人都可以解释一下 为什么会发生这种情况?
I've defined two simple Python functions that take a single argument, raise an exception, and handle the raised exception. One function uses a variable to refer to the exception before raising/handling, the other does not:
def refcount_unchanged(x):
try:
raise Exception()
except:
pass
def refcount_increases(x):
e = Exception()
try:
raise e
except:
pass
One of the resulting functions increases pythons refcount
for its input argument, the other does not:
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
Can anyone explain why this happens?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是 PEP-344 (Python 2.5),并在以下情况下解决PEP-3110 中的
refcount_unchanged
(Python 3.0 )。在
refcount_increases
中,可以通过打印以下内容来观察引用循环:这表明
x
也在帧的局部变量中被引用。当垃圾收集器运行或调用
gc.collect()
时,引用循环就会被解决。在 refcount_unchanged 中,根据 PEP-3110 的 语义变化,Python 3 生成额外的字节码来删除目标,从而消除引用循环:
被翻译为类似:
解决引用循环
refcount_increases
虽然没有必要(因为垃圾收集器将完成其工作),但您可以通过手动删除变量引用来在
refcount_increases
中执行类似的操作:或者,您可以覆盖变量引用并让隐式删除起作用:
关于引用循环的更多信息
异常
e
和其他局部变量实际上是由e.__traceback__.tb_frame
直接引用的,大概是在C中 代码。这可以通过打印以下
内容来观察:访问 e.__traceback__.tb_frame.f_locals 创建一个缓存在帧上的字典(另一个引用周期)并阻止上面的主动解决方案。
然而,这个引用周期也将由垃圾收集器处理。
It is a side effect of the "exception -> traceback -> stack frame -> exception" reference cycle from the
__traceback__
attribute on exception instances introduced in PEP-344 (Python 2.5), and resolved in cases likerefcount_unchanged
in PEP-3110 (Python 3.0).In
refcount_increases
, the reference cycle can be observed by printing this:which shows that
x
is also referenced in the frame's locals.The reference cycle is resolved when the garbage collector runs, or if
gc.collect()
is called.In
refcount_unchanged
, as per PEP-3110's Semantic Changes, Python 3 generates additional bytecode to delete the target, thus eliminating the reference cycle:gets translated to something like:
Resolving the reference cycle in
refcount_increases
While not necessary (since the garbage collector will do its job), you can do something similar in
refcount_increases
by manually deleting the variable reference:Alternatively, you can overwrite the variable reference and let the implicit deletion work:
A little more about the reference cycle
The exception
e
and other local variables are actually referenced directly bye.__traceback__.tb_frame
, presumably in C code.This can be observed by printing this:
Accessing
e.__traceback__.tb_frame.f_locals
creates a dictionary cached on the frame (another reference cycle) and thwarts the proactive resolutions above.However, this reference cycle will also be handled by the garbage collector.
看来写出这个问题帮助我们实现了部分答案。如果我们在每次调用
refcount_increases
后进行垃圾收集,则引用计数不再增加。有趣的!我认为这不是我们问题的完整答案,但它确实具有启发性。欢迎任何进一步的信息。It seems that writing out the question helped us realize part of the answer. If we garbage-collect after each call to
refcount_increases
, the refcount no longer increases. Interesting! I don't think this is a complete answer to our question, but it's certainly suggestive. Any further information would be welcome.