2012-04-18 68 views
6

這是CPython 2.7.2和3.2.2。爲什麼一些引用``x.y``的表達式改變``id(x.y)``?

假設我們定義如下Classobj

class Class(object): 

    def m(self): 
     pass 

    @property 
    def p(self): 
     return None 

    @staticmethod 
    def s(): 
     pass 

obj = Class() 

短版

爲什麼每個print()下面的代碼輸出False

print(Class.__dict__ is Class.__dict__) 
print(Class.__subclasshook__ is Class.__subclasshook__) 
print(Class.m is Class.m) 

print(obj.__delattr__ is obj.__delattr__) 
print(obj.__format__ is obj.__format__) 
print(obj.__getattribute__ is obj.__getattribute__) 
print(obj.__hash__ is obj.__hash__) 
print(obj.__init__ is obj.__init__) 
print(obj.__reduce__ is obj.__reduce__) 
print(obj.__reduce_ex__ is obj.__reduce_ex__) 
print(obj.__repr__ is obj.__repr__) 
print(obj.__setattr__ is obj.__setattr__) 
print(obj.__sizeof__ is obj.__sizeof__) 
print(obj.__str__ is obj.__str__) 
print(obj.__subclasshook__ is obj.__subclasshook__) 
print(obj.m is obj.m) 

(這是在Python 2;對於Python 3中,省略了print()Class.m並添加類似於那些爲obj.__eq__obj.__ge__obj.__gt__obj.__le__obj.__lt__obj.__ne__

,爲什麼,相比之下,以下代碼是否爲print()輸出True

print(Class.__class__ is Class.__class__) 
print(Class.__delattr__ is Class.__delattr__) 
print(Class.__doc__ is Class.__doc__) 
print(Class.__format__ is Class.__format__) 
print(Class.__getattribute__ is Class.__getattribute__) 
print(Class.__hash__ is Class.__hash__) 
print(Class.__init__ is Class.__init__) 
print(Class.__module__ is Class.__module__) 
print(Class.__new__ is Class.__new__) 
print(Class.__reduce__ is Class.__reduce__) 
print(Class.__reduce_ex__ is Class.__reduce_ex__) 
print(Class.__repr__ is Class.__repr__) 
print(Class.__setattr__ is Class.__setattr__) 
print(Class.__sizeof__ is Class.__sizeof__) 
print(Class.__str__ is Class.__str__) 
print(Class.__weakref__ is Class.__weakref__) 
print(Class.p is Class.p) 
print(Class.s is Class.s) 

print(obj.__class__ is obj.__class__) 
print(obj.__dict__ is obj.__dict__) 
print(obj.__doc__ is obj.__doc__) 
print(obj.__module__ is obj.__module__) 
print(obj.__new__ is obj.__new__) 
print(obj.__weakref__ is obj.__weakref__) 
print(obj.p is obj.p) 
print(obj.s is obj.s) 

(這是在Python 2;爲Python 3,對於Class.__eq__Class.__ge__Class.__gt__Class.__le__Class.__lt__Class.__ne__添加類似於print()秒,Class.m

長版

如果我們連續詢問id(obj.m)兩次,然後我們得到相同的ID兩次。

>>> id(obj.m) 
139675714789856 
>>> id(obj.m) 
139675714789856 

但如果我們要求id(obj.m),然後一些表達式包含對obj.m,然後再id(obj.m),該ID的變化有時(但不總是)。如果它改變了,那麼如果我們再次要求id(obj.m),則ID有時(但並非總是)變回原始值。如果它沒有變回,那麼看起來重複中間表達式會導致ID在兩個值之間交替。

這裏是一些例子,其中id(obj.m)不會改變:

>>> print(obj.m); id(obj.m) 
<bound method Class.m of <__main__.Class object at 0x7f08c96058d0>> 
139675714789856 
>>> obj.m is None; id(obj.m) 
False 
139675714789856 
>>> obj.m.__func__.__name__; id(obj.m) 
'm' 
139675714789856 
>>> obj.m(); id(obj.m) 
139675714789856 

這裏是一個例子,其中id(obj.m)變更,然後變回:

>>> obj.m; id(obj.m); id(obj.m) 
<bound method Class.m of <__main__.Class object at 0x7f08c96058d0>> 
139675715407536 
139675714789856 

這裏是一個例子,其中id(obj.m)變化,然後不改回:

>>> obj.m is obj.m; id(obj.m); id(obj.m) 
False 
139675715407536 
139675715407536 

這裏是同樣的表情,反覆幾次後證明交替行爲:

>>> obj.m is obj.m; id(obj.m); id(obj.m) 
False 
139675714789856 
139675714789856 
>>> obj.m is obj.m; id(obj.m); id(obj.m) 
False 
139675715407536 
139675715407536 
>>> obj.m is obj.m; id(obj.m); id(obj.m) 
False 
139675714789856 
139675714789856 

因此我的整個問題是

  • 什麼樣的屬性,可能會改變他們的身份表達的副作用那些不會修改這些屬性?
  • 什麼樣的表達式觸發這樣的改變?
  • 導致這種變化的機制是什麼?
  • 在什麼條件下回收過去的身份?
  • 爲什麼不是無限循環的第一個身份,這將避免所有這些併發症?
  • 是否有任何記錄?

回答

5

什麼類型的屬性可能會改變它們的身份作爲不修改這些屬性的表達式的副作用?

屬性,或更準確地說實現了descriptor protocol的對象。例如,Class.__dict__不是dict,而是dictproxy。顯然,每次請求時都會重新生成此對象。爲什麼?可能要減少創建對象的開銷,直到有必要這樣做。但是,這是一個實現細節。重要的是__dict__按照記錄工作。

即使是普通的實例方法也是使用描述符處理的,這就解釋了爲什麼obj.m is not obj.m。有趣的是,如果你做obj.m = obj.m永久存儲該實例的方法包裝,然後obj.m is obj.m。 :-)

什麼樣的表達式觸發這樣的改變?

對屬性的任何訪問都可以觸發描述符的方法,並且此方法可以始終返回相同的對象或每次返回一個不同的對象。

什麼是導致這種變化的機制?

屬性/描述符。

在什麼條件下回收過去的身份?

不確定你的意思是「回收」。你的意思是「處置」或「重用」?在CPython中,對象的id是它的內存位置。如果兩個對象在不同時間在同一個內存位置結束,它們將具有相同的id。因此,在不同時間(即使在一個語句中)具有相同的相同id的兩個參考不一定是相同的對象。其他Python實現使用不同的規則來生成id。例如,我相信Jython使用遞增整數,這可以提供更清晰的對象標識。

爲什麼不是無限循環的第一個身份,這將避免所有這些併發症?

推測使用描述符有一些優點。 Python解釋器的源代碼可用;看看,如果你想知道更多的細節。

是否有這種記錄?

不。這些是CPython解釋器的特定於實現的細節,不應該依賴。其他Python實現(包括CPython的未來版本)可能並最有可能會表現不同。例如,在2.x和3.x CPython之間存在顯着差異。

2

當您編寫x.y時,會創建一個綁定或未綁定的方法。這是一個新的對象。它可以在任何地方記憶。如果你寫x.y並且不使用結果,它的refcnt可以被歸零。這意味着內存可用,並且可以在下一個x.y中使用,可能在相同的位置,但不一定。

請注意CPython關於對象標識的保證很少(即保證只有一個);否則,你所看到的很多是可能改變的任意實現選擇。

+0

@ kindall的回答解決了我的大部分問題,但是這解決了有關回收ID的子問題。謝謝。 – nisavid 2013-05-21 16:35:00

相關問題