2013-12-24 37 views
23

我在python中做了一些事情(使用python 3.3.3),並且我遇到了一些令我困惑的事情,因爲我的理解類每次調用時都會獲得一個新的id。爲什麼Python類的id在快速調用時不唯一?

比方說,你在一些.py文件有這樣的:

class someClass: pass 

print(someClass()) 
print(someClass()) 

以上的回報這是困惑我,因爲我在它調用所以它不應該是一樣的吧相同的ID?當同一個類連續調用兩次時,python是如何工作的?當我等待幾秒鐘時,它給出了一個不同的ID,但是如果我像上面的例子那樣做,它似乎不會以這種方式工作,這使我感到困惑。

>>> print(someClass());print(someClass()) 
<__main__.someClass object at 0x0000000002D96F98> 
<__main__.someClass object at 0x0000000002D96F98> 

它返回相同的東西,但爲什麼?我也注意到它與範圍例如

for i in range(10): 
    print(someClass()) 

是否有什麼特別的原因讓python在快速調用類時執行此操作?我甚至不知道python做到了這一點,或者它可能是一個bug?如果它不是一個bug,有人可以向我解釋如何修復它或一個方法,以便每次調用方法/類時它會生成一個不同的id?我很困惑這是怎麼回事,因爲如果我等待,它確實會改變,但如果我嘗試兩次或更多次調用同一個類,則不會改變。

回答

25

對象的id只保證在該對象的生存期內是唯一的,而不是在程序的整個生命週期中。您創建的兩個someClass對象僅在調用print期間存在 - 此後,它們可用於垃圾回收(並且在CPython中,立即釋放)。由於他們的生活時間不重疊,所以他們共享一個ID是有效的。

在這種情況下,由於兩個CPython實現細節的組合,它也是不會出現的:首先,它通過引用計數進行垃圾回收(帶有一些額外的魔法以避免循環引用的問題);其次,一個對象與變量底層指針的值(即它的內存位置)有關。因此,第一個對象,即最近分配的對象,立即被釋放 - 分配的對象將最終放在同一個位置並不令人感到意外(儘管這也可能取決於解釋器的細節被編譯)。

如果您依賴於具有不同id s的多個對象,則可以將它們放在列表中,以便使它們的生命週期重疊。否則,可能會實現一個特定的類ID有不同的保障 - 如:

class SomeClass: 
    next_id = 0 

    def __init__(self): 
     self.id = SomeClass.nextid 
     SomeClass.nextid += 1 
+5

很好的解釋,但一個小小的狡辯。寫入的方式意味着內存實際上獲得'free'd,然後是'malloc'd(或者一些等價的),當它甚至沒有超出Python的PyObject自由列表時,並且_that's_爲什麼它發生得如此一致(取決於你的解釋注意事項),甚至跨平臺或調試malloc等等。 – abarnert

+1

基礎對象''tp_dealloc'調用['PyObject_GC_Del'] [堆類型的'tp_free'](http://hg.python.org/cpython/file/c3896275c0f6/Objects/typeobject.c#l2370) (http://hg.python.org/cpython/file/c3896275c0f6/Modules/gcmodule.c#l1621)。這反過來使用宏'PyObject_FREE'。關於如何編譯CPython的警告是[沒有pymalloc](http://hg.python.org/cpython/file/c3896275c0f6/Include/objimpl.h#l133)宏PyObject_FREE被定義爲PyMem_FREE ',這對於非調試版本來說是「免費」的。所以在這一點上,地址重用取決於平臺'malloc'。 – eryksun

+0

說到提及垃圾收集:)。 – ivanleoncz

3

它釋放的第一個實例,因爲它沒有被保留,然後因爲在此期間內存沒有發生任何事情,它實例化第二次到同一個位置。

+0

哦,我明白了,有沒有什麼辦法可以告訴python內存改變了,所以它實例化的方式不同?我不知道如何快速改變內存,因此每次都會分配一個不同的ID。 – user3130555

+0

我不會使用該ID作爲您的標識符。要麼傳入並存儲計數器變量,要麼使用id,請將實例添加到列表或其他對象中,以防止重複使用。 – mhlester

+2

我不知道你爲什麼需要不同的ID,但是無論你是什麼原因,這可能是錯誤的。你也必須考慮到,由於內部的「緩存」,它可能會發生(不可變類型)兩個不同的和顯然無關的變量共享相同的對象(和ID)。 – smeso

3

試試這個,嘗試調用以下:

a = someClass() 
for i in range(0,44): 
    print(someClass()) 
print(a) 

你會看到不同的東西。爲什麼?導致由「foo」循環中的第一個對象釋放的內存被重用。另一方面,a不被重用,因爲它被保留。

13

如果你讀的文檔id,它說:

返回的對象的「身份」。 這是一個整數,它在其生命週期中保證對這個對象唯一且恆定。具有非重疊壽命的兩個對象可能具有相同的id()值。

而這正是發生了什麼:你有不重疊的壽命兩個對象,因爲有史以來第二個之前,第一個是已經超出了範圍。


但不要相信,這將總是發生,無論是。特別是如果你需要處理其他的Python實現或更復雜的類。語言所說的是,這兩個對象可能具有相同的值id(),而不是他們。而事實上,他們取決於兩個實施細則:

  • 垃圾收集器清理的第一個對象之前,你的代碼甚至開始分配保證與CPython的或發生其中的第二對象任何其他的ref-counting實現(當沒有循環引用時),但在Jython或IronPython中使用世代垃圾回收器的可能性很小。

  • 下面的分配器必須非常喜歡重新使用相同類型的最近釋放的對象。這在CPython中是真實的,它在基本C malloc之上有多層花式分配器,但大多數其他實現會將更多內容留給底層虛擬機。


最後一件事:即object.__repr__恰好包含出現這種情況是一樣的id子字符串爲十六進制數只是一個CPython的實施工件,不被任何保證事實。據the docs

如果可能的話,這應該是一個可以用來重建具有相同的值(給定一個適當的環境)的對象的有效的Python表達式。如果這不可行,則應返回<...some useful description…>形式的字符串。

是CPython中的object恰好把hex(id(self))(實際上事實上,我認爲它做的sprintf相當於通過%p -ing它的指針,但由於CPython中的id剛剛返回相同的指針強制轉換爲long那最終被相同)不能保證在任何地方。即使在object甚至早於2.x天存在之前,它也是如此。在交互式提示符下調試這種簡單的「這裏發生了什麼」,您可以安全地使用它,但不要試圖在超出此範圍的情況下使用它。

4

我覺得這裏有一個更深層次的問題。您不應該依靠id來跟蹤程序生命週期中的獨特實例。您應該簡單地將其視爲每個對象實例持續時間內的無保證內存位置指示符。如果您立即創建並釋放實例,那麼您可能會在同一內存位置創建連續的實例。

也許你需要做的是跟蹤一個類的靜態計數器,它爲每個新實例分配一個唯一的ID,併爲下一個實例遞增類靜態計數器。

+0

我不認爲OP在這裏嘗試使用'id'(或者實際上,出現在'repr'中的等效數字)用於調試對象生命週期以外的任何目的......這是它的一個好處。 – abarnert

+0

@abarnert如果你看到OP在mhlester的回答中的評論,這似乎表明OP實際上正在尋找這樣的等效行爲。 –

+0

雖然從他對同一個答案的後續評論看來,他似乎並沒有真的在尋找那個,他只是在調試的時候感到困惑...... – abarnert

0

在存儲位置(和id)不能被釋放的例子是:

print([someClass() for i in range(10)]) 

現在的ID都是唯一的。

相關問題