了解 python 引用计数以便调试 C 扩展
understanding python reference counts in order to debug c-extensions
我正在写一个 c 扩展,想在 pytest 中测试它。
我正在测试的部分内容是我的对象的引用计数是否正确。因此,我在纯 python 中构建了一个小测试,这让我感到困惑...
从 Ipython 我得到:
In [1]: x = 153513514215
In [2]: import sys
In [3]: sys.getrefcount(x)
Out[3]: 2
太好了,1 个参考来自作业,1 个来自调用者。
但是下面的脚本 (Whosebug_test.py) 给出了下面的结果
import sys
def test_ref_count_int():
x = 677461248192962146784178
assert sys.getrefcount(x) == 2
def test_ref_count_str():
y = 'very long and probbably very unique string'
assert sys.getrefcount(y) == 2
def normal_te_st():
x = 222677461248192962146784178
y = '!!!!very long and probbably unique string!!!!'
print ('x refcount = {}'.format(sys.getrefcount(x)))
print ('y refcount = {}'.format(sys.getrefcount(y)))
if __name__ == '__main__':
normal_te_st()
当我 运行 它作为一个普通的 python 脚本时
$ python3 Whosebug_test.py
x refcount = 4
y refcount = 4
为什么是 4 而不是 2?
当我 运行 它与 pytest
$ python3 -m pytest Whosebug_test.py
=================== test session starts ===================
platform linux -- Python 3.4.3, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
rootdir: /opt/projects/0001_Intomics/00005_TextMining/jcr/textmining/tests, inifile:
collected 2 items
Whosebug_test.py FF
======================== FAILURES =========================
___________________ test_ref_count_int ____________________
def test_ref_count_int():
x = 677461248192962146784178
> assert sys.getrefcount(x) == 2
E assert 3 == 2
E + where 3 = <built-in function getrefcount>(677461248192962146784178)
E + where <built-in function getrefcount> = sys.getrefcount
Whosebug_test.py:7: AssertionError
___________________ test_ref_count_str ____________________
def test_ref_count_str():
y = 'very long and probbably very unique string'
> assert sys.getrefcount(y) == 2
E AssertionError: assert 3 == 2
E + where 3 = <built-in function getrefcount>('very long and probbably very unique string')
E + where <built-in function getrefcount> = sys.getrefcount
Whosebug_test.py:11: AssertionError
为什么是 3 而不是 2?
问题:怎么会这样
- python = 4 个参考计数
- pytest = 3 个引用计数
- ipython session = 2 次引用计数
我希望它在所有 3 种情况下都像 ipython 中那样表现,任何人都可以解释发生了什么,并给我一些提示如何最好地测试我正在创建的对象。
代码中的文字存储在代码对象中。字节码栈另作参考:
>>> import dis
>>> def normal_te_st():
... x = 222677461248192962146784178
... y = '!!!!very long and probbably unique string!!!!'
... print ('x refcount = {}'.format(sys.getrefcount(x)))
... print ('y refcount = {}'.format(sys.getrefcount(y)))
...
>>> normal_te_st.__code__.co_consts
(None, 222677461248192962146784178, '!!!!very long and probbably unique string!!!!', 'x refcount = {}', 'y refcount = {}')
>>> dis.dis(normal_te_st)
2 0 LOAD_CONST 1 (222677461248192962146784178)
2 STORE_FAST 0 (x)
3 4 LOAD_CONST 2 ('!!!!very long and probbably unique string!!!!')
6 STORE_FAST 1 (y)
4 8 LOAD_GLOBAL 0 (print)
10 LOAD_CONST 3 ('x refcount = {}')
12 LOAD_ATTR 1 (format)
14 LOAD_GLOBAL 2 (sys)
16 LOAD_ATTR 3 (getrefcount)
18 LOAD_FAST 0 (x)
20 CALL_FUNCTION 1
22 CALL_FUNCTION 1
24 CALL_FUNCTION 1
26 POP_TOP
5 28 LOAD_GLOBAL 0 (print)
30 LOAD_CONST 4 ('y refcount = {}')
32 LOAD_ATTR 1 (format)
34 LOAD_GLOBAL 2 (sys)
36 LOAD_ATTR 3 (getrefcount)
38 LOAD_FAST 1 (y)
40 CALL_FUNCTION 1
42 CALL_FUNCTION 1
44 CALL_FUNCTION 1
46 POP_TOP
48 LOAD_CONST 0 (None)
50 RETURN_VALUE
LOAD_CONST
操作码从附加到代码对象的 co_consts
元组加载对象;该元组是一个参考。 STORE_FAST
然后将其放入局部变量,即第二个引用。
然后是 LOAD_FAST
操作码,它从本地存储中获取一个名称并将其放入堆栈,again incrementing the reference count。
最后但同样重要的是,您将该值传递给 sys.getrefcount()
调用。
如果您想了解哪些引用了您的对象,您可能需要查看 gc.get_referrers()
;此函数在调用时排除自身和堆栈,因此您可以在心里添加 +2:
>>> import gc
>>> def gc_demo():
... x = 222677461248192962146784178
... print(gc.get_referrers(x))
...
>>> gc_demo()
[(None, 222677461248192962146784178), <frame object at 0x106a25a98>]
打印 2 个对象; co_consts
元组和当前调用帧(对于本地人)。
py.test
做了一些额外的 import-time magic which rewrites assert
statements,结果引用计数又不同了。
您可能还想阅读相同的 Reference Counts section of the Extending Python with C or C++ documentation, the Objects, Types and Reference Counts section of the C API Reference Manual, and last but not least the Debugging Builds section,以了解如何创建一个 Python 构建来帮助您详细跟踪引用计数。
您永远不应依赖对对象的特定数量的引用。例如,我可以通过进入函数对象来简单地为您的对象添加更多引用(foo = normal_te_st.__code__.co_conts[1]
甚至会在 运行 函数之前增加引用计数)。真正需要引用计数上升的是一个实现细节。只需确保您自己的代码 正确处理引用即可。
我正在写一个 c 扩展,想在 pytest 中测试它。
我正在测试的部分内容是我的对象的引用计数是否正确。因此,我在纯 python 中构建了一个小测试,这让我感到困惑...
从 Ipython 我得到:
In [1]: x = 153513514215
In [2]: import sys
In [3]: sys.getrefcount(x)
Out[3]: 2
太好了,1 个参考来自作业,1 个来自调用者。
但是下面的脚本 (Whosebug_test.py) 给出了下面的结果
import sys
def test_ref_count_int():
x = 677461248192962146784178
assert sys.getrefcount(x) == 2
def test_ref_count_str():
y = 'very long and probbably very unique string'
assert sys.getrefcount(y) == 2
def normal_te_st():
x = 222677461248192962146784178
y = '!!!!very long and probbably unique string!!!!'
print ('x refcount = {}'.format(sys.getrefcount(x)))
print ('y refcount = {}'.format(sys.getrefcount(y)))
if __name__ == '__main__':
normal_te_st()
当我 运行 它作为一个普通的 python 脚本时
$ python3 Whosebug_test.py
x refcount = 4
y refcount = 4
为什么是 4 而不是 2?
当我 运行 它与 pytest
$ python3 -m pytest Whosebug_test.py
=================== test session starts ===================
platform linux -- Python 3.4.3, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
rootdir: /opt/projects/0001_Intomics/00005_TextMining/jcr/textmining/tests, inifile:
collected 2 items
Whosebug_test.py FF
======================== FAILURES =========================
___________________ test_ref_count_int ____________________
def test_ref_count_int():
x = 677461248192962146784178
> assert sys.getrefcount(x) == 2
E assert 3 == 2
E + where 3 = <built-in function getrefcount>(677461248192962146784178)
E + where <built-in function getrefcount> = sys.getrefcount
Whosebug_test.py:7: AssertionError
___________________ test_ref_count_str ____________________
def test_ref_count_str():
y = 'very long and probbably very unique string'
> assert sys.getrefcount(y) == 2
E AssertionError: assert 3 == 2
E + where 3 = <built-in function getrefcount>('very long and probbably very unique string')
E + where <built-in function getrefcount> = sys.getrefcount
Whosebug_test.py:11: AssertionError
为什么是 3 而不是 2?
问题:怎么会这样
- python = 4 个参考计数
- pytest = 3 个引用计数
- ipython session = 2 次引用计数
我希望它在所有 3 种情况下都像 ipython 中那样表现,任何人都可以解释发生了什么,并给我一些提示如何最好地测试我正在创建的对象。
代码中的文字存储在代码对象中。字节码栈另作参考:
>>> import dis
>>> def normal_te_st():
... x = 222677461248192962146784178
... y = '!!!!very long and probbably unique string!!!!'
... print ('x refcount = {}'.format(sys.getrefcount(x)))
... print ('y refcount = {}'.format(sys.getrefcount(y)))
...
>>> normal_te_st.__code__.co_consts
(None, 222677461248192962146784178, '!!!!very long and probbably unique string!!!!', 'x refcount = {}', 'y refcount = {}')
>>> dis.dis(normal_te_st)
2 0 LOAD_CONST 1 (222677461248192962146784178)
2 STORE_FAST 0 (x)
3 4 LOAD_CONST 2 ('!!!!very long and probbably unique string!!!!')
6 STORE_FAST 1 (y)
4 8 LOAD_GLOBAL 0 (print)
10 LOAD_CONST 3 ('x refcount = {}')
12 LOAD_ATTR 1 (format)
14 LOAD_GLOBAL 2 (sys)
16 LOAD_ATTR 3 (getrefcount)
18 LOAD_FAST 0 (x)
20 CALL_FUNCTION 1
22 CALL_FUNCTION 1
24 CALL_FUNCTION 1
26 POP_TOP
5 28 LOAD_GLOBAL 0 (print)
30 LOAD_CONST 4 ('y refcount = {}')
32 LOAD_ATTR 1 (format)
34 LOAD_GLOBAL 2 (sys)
36 LOAD_ATTR 3 (getrefcount)
38 LOAD_FAST 1 (y)
40 CALL_FUNCTION 1
42 CALL_FUNCTION 1
44 CALL_FUNCTION 1
46 POP_TOP
48 LOAD_CONST 0 (None)
50 RETURN_VALUE
LOAD_CONST
操作码从附加到代码对象的 co_consts
元组加载对象;该元组是一个参考。 STORE_FAST
然后将其放入局部变量,即第二个引用。
然后是 LOAD_FAST
操作码,它从本地存储中获取一个名称并将其放入堆栈,again incrementing the reference count。
最后但同样重要的是,您将该值传递给 sys.getrefcount()
调用。
如果您想了解哪些引用了您的对象,您可能需要查看 gc.get_referrers()
;此函数在调用时排除自身和堆栈,因此您可以在心里添加 +2:
>>> import gc
>>> def gc_demo():
... x = 222677461248192962146784178
... print(gc.get_referrers(x))
...
>>> gc_demo()
[(None, 222677461248192962146784178), <frame object at 0x106a25a98>]
打印 2 个对象; co_consts
元组和当前调用帧(对于本地人)。
py.test
做了一些额外的 import-time magic which rewrites assert
statements,结果引用计数又不同了。
您可能还想阅读相同的 Reference Counts section of the Extending Python with C or C++ documentation, the Objects, Types and Reference Counts section of the C API Reference Manual, and last but not least the Debugging Builds section,以了解如何创建一个 Python 构建来帮助您详细跟踪引用计数。
您永远不应依赖对对象的特定数量的引用。例如,我可以通过进入函数对象来简单地为您的对象添加更多引用(foo = normal_te_st.__code__.co_conts[1]
甚至会在 运行 函数之前增加引用计数)。真正需要引用计数上升的是一个实现细节。只需确保您自己的代码 正确处理引用即可。