2

我正在編寫c-extension並希望在pytest中測試它。瞭解python引用計數以調試c-extensions

我測試的部分內容是引用計數是否在我的對象上正確。因此,我在建純Python一個小的測試,讓我爲難......

從IPython中我得到:

In [1]: x = 153513514215 

In [2]: import sys 

In [3]: sys.getrefcount(x) 
Out[3]: 2 

所以票價從分配好,1個參考和1從主叫方。

但是下面的腳本(stackoverflow_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 stackoverflow_test.py 
x refcount = 4 
y refcount = 4 

爲什麼4,而不是2?

當我與pytest

$ python3 -m pytest stackoverflow_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 

stackoverflow_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 

stackoverflow_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 

stackoverflow_test.py:11: AssertionError 

爲什麼3而不是2運行呢?

問:怎麼說,

  • 蟒蛇= 4裁判計數
  • pytest = 3裁判計數
  • IPython的會話= 2裁判計數

我就指望它在所有3種情況下都像ipython一樣行事,任何人都可以解釋發生了什麼,並給出一些提示,以便如何最好地測試我創建的對象。

回答

4

代碼中的文字存儲在代碼對象中。字節碼堆棧是另一參考:

>>> 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其中rewrites assert statements,結果引用計數再次不同。

您可能還需要閱讀擴展Python用C或C++文件的Reference Counts section,該C API參考手冊,最後但同樣相同的並非最不重要的Debugging Builds sectionObjects, Types and Reference Counts section,學習如何創建一個Python構建,幫助您詳細跟蹤引用計數。

你不應該依賴於一個對象的特定數量的引用。例如,我可以通過訪問函數對象來爲您的對象添加更多的引用,例如(即使運行該函數之前,foo = normal_te_st.__code__.co_conts[1]也會增加引用計數)。究竟需要引用計數是一個實現細節。只要確保您自己的代碼正確處理引用。

+0

感謝您的參考,我需要咀嚼它,但這個答案是我所希望的... – jcr