gpt4 book ai didi

python - 为什么 Python 中新创建的变量的引用计数为四?

转载 作者:太空狗 更新时间:2023-10-29 17:06:31 24 4
gpt4 key购买 nike

我一直在为同事准备一个演示文稿,以解释 GIL 的基本行为和背后的推理,并在整理引用计数的快速解释时发现了一些我无法解释的内容。看起来新声明的变量有四个引用,而不是我期望的那个。例如下面的代码:

the_var = 'Hello World!'
print('Var created: {} references'.format(sys.getrefcount(the_var)))

此输出结果:

Var created: 4 references

如果我使用大于 100 的整数(< 100 是预先创建的并且具有更大的引用计数)或 float ,并且如果我在函数范围内或循环中声明变量,我验证了输出是相同的.结果是一样的。该行为在 2.7.11 和 3.5.1 中似乎也相同。

我试图调试 sys.getrefcount 以查看它是否正在创建额外的引用,但无法进入该函数(我假设它是直接向下传递到 C 层)。

我知道我在演讲时至少会收到一个关于此的问题,但实际上我对结果感到非常困惑。任何人都可以向我解释这种行为吗?

最佳答案

有几种情况会产生不同的引用计数。最直接的是 REPL 控制台:

>>> import sys
>>> the_var = 'Hello World!'
>>> print(sys.getrefcount(the_var))
2

理解这个结果非常简单 - 本地堆栈中有一个引用,另一个临时/本地引用 sys.getrefcount() 函数(甚至 documentation 对此发出警告 - 返回的计数通常比您预期的多 1)。但是当您将它作为独立脚本运行时:

import sys

the_var = 'Hello World!'
print(sys.getrefcount(the_var))
# 4

如您所见,您得到了 4。那么给出了什么?好吧,让我们调查一下……垃圾收集器有一个非常有用的接口(interface) - gc 模块 - 所以如果我们在 REPL 控制台中运行它:

>>> import gc
>>> the_var = 'Hello World!'
>>> gc.get_referrers(the_var)
[{'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'the_var': 'Hello
World!', 'gc': <module 'gc' (built-in)>, '__name__': '__main__', '__doc__': None}]

难怪,- 这实际上只是当前命名空间 (locals()),因为该变量在其他任何地方都不存在。但是当我们将其作为独立脚本运行时会发生什么:

import gc
import pprint

the_var = 'Hello World!'
pprint.pprint(gc.get_referrers(the_var))

这会打印出来(YMMV,基于你的 Python 版本):

[['gc',
'pprint',
'the_var',
'Hello World!',
'pprint',
'pprint',
'gc',
'get_referrers',
'the_var'],
(-1, None, 'Hello World!'),
{'__builtins__': <module '__builtin__' (built-in)>,
'__doc__': None,
'__file__': 'test.py',
'__name__': '__main__',
'__package__': None,
'gc': <module 'gc' (built-in)>,
'pprint': <module 'pprint' from 'D:\Dev\Python\Py27-64\lib\pprint.pyc'>,
'the_var': 'Hello World!'}]

果然,正如 sys.getrefcount() 告诉我们的那样,列表中还有两个引用,但那些到底是什么?好吧,当 Python 解释器解析你的脚本时,它首先需要 compile它到字节码 - 当它这样做时,它将所有字符串存储在一个列表中,因为它也提到了你的变量,被声明为对它的引用。

第二个更神秘的条目 ((-1, None, 'Hello World!')) 来自 peep-hole optimizer是否只是优化访问(在这种情况下是字符串引用)。

这两者都是临时的和可选的 - REPL 控制台正在进行上下文分离,因此如果您要从当前上下文“外包”编译,您将看不到这些引用:

import gc
import pprint

exec(compile("the_var = 'Hello World!'", "<string>", "exec"))
pprint.pprint(gc.get_referrers(the_var))

你会得到:

[{'__builtins__': <module '__builtin__' (built-in)>,
'__doc__': None,
'__file__': 'test.py',
'__name__': '__main__',
'__package__': None,
'gc': <module 'gc' (built-in)>,
'pprint': <module 'pprint' from 'D:\Dev\Python\Py27-64\lib\pprint.pyc'>,
'the_var': 'Hello World!'}]

如果您要返回最初尝试通过 sys.getreferencecount() 获取引用计数:

import sys

exec(compile("the_var = 'Hello World!'", "<string>", "exec"))
print(sys.getrefcount(the_var))
# 2

就像在 REPL 控制台中一样,正如预期的那样。由于窥孔优化而产生的额外引用,因为它就地发生,可以在计算引用之前通过强制垃圾收集 (gc.collect()) 立即丢弃。

但是,在编译期间创建的字符串列表在整个文件被解析和编译之前无法释放,这就是为什么如果您要在另一个脚本中导入脚本然后计算对 the_var 的引用 从它你会得到 3 而不是 4 就在你认为它不会再让你感到困惑的时候;)

关于python - 为什么 Python 中新创建的变量的引用计数为四?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45021901/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com