了解 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?

问题:怎么会这样

我希望它在所有 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] 甚至会在 运行 函数之前增加引用计数)。真正需要引用计数上升的是一个实现细节。只需确保您自己的代码 正确处理引用即可。