为什么为 Python 函数中引发的异常创建变量名会影响该函数的输入变量的引用计数?

发布于 2025-01-10 03:01:49 字数 695 浏览 1 评论 0原文

我定义了两个简单的 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夏尔 2025-01-17 03:01:49

这是 PEP-344 (Python 2.5),并在以下情况下解决PEP-3110 中的 refcount_unchanged (Python 3.0 )。

refcount_increases中,可以通过打印以下内容来观察引用循环:

except:
    print(e.__traceback__.tb_frame.f_locals)  # {'x': [], 'e': Exception()}

这表明x也在帧的局部变量中被引用。

当垃圾收集器运行或调用 gc.collect() 时,引用循环就会被解决。

在 refcount_unchanged 中,根据 PEP-3110 的 语义变化,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()}

然而,这个引用周期也将由垃圾收集器处理。

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 like refcount_unchanged in PEP-3110 (Python 3.0).

In refcount_increases, the reference cycle can be observed by printing this:

except:
    print(e.__traceback__.tb_frame.f_locals)  # {'x': [], 'e': Exception()}

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:

def refcount_unchanged(x):
    try:
        raise Exception()
    except:
        pass

gets translated to something like:

def refcount_unchanged(x):
    try:
        raise Exception()
    except Exception as e:
        try:
            pass
        finally:
            e = None
            del e

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:

def refcount_increases(x):
    e = Exception()
    try:
        raise e
    except:
        pass
    finally:   # +
        del e  # +

Alternatively, you can overwrite the variable reference and let the implicit deletion work:

def refcount_increases(x):
    e = Exception()
    try:
        raise e
    # except:               # -
    except Exception as e:  # +
        pass

A little more about the reference cycle

The exception e and other local variables are actually referenced directly by e.__traceback__.tb_frame, presumably in C code.

This can be observed by printing this:

print(sys.getrefcount(b))
print(gc.get_referrers(b)[0])  # <frame at ...>

Accessing e.__traceback__.tb_frame.f_locals creates a dictionary cached on the frame (another reference cycle) and thwarts the proactive resolutions above.

print(sys.getrefcount(b))
print(gc.get_referrers(b)[0])  # {'x': [], 'e': Exception()}

However, this reference cycle will also be handled by the garbage collector.

护你周全 2025-01-17 03:01:49

看来写出这个问题帮助我们实现了部分答案。如果我们在每次调用refcount_increases后进行垃圾收集,则引用计数不再增加。有趣的!我认为这不是我们问题的完整答案,但它确实具有启发性。欢迎任何进一步的信息。

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

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.

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文