簡體   English   中英

為什么我不能更改對象實例的 __class__ 屬性?

[英]Why can't I change the __class__ attribute of an instance of object?

class A(object):
    pass

class B(A):
    pass

o = object()
a = A()
b = B()

雖然我可以更改a.__class__ ,但我不能對o.__class__做同樣的事情(它會引發TypeError錯誤)。 為什么?

例如:

isinstance(a, A) # True
isinstance(a, B) # False
a.__class__ = B
isinstance(a, A) # True
isinstance(a, B) # True

isinstance(o, object) # True
isinstance(o, A) # False
o.__class__ = A # This fails and throws a TypeError
# isinstance(o, object)
# isinstance(o, A)

我知道這通常不是一個好主意,因為如果處理不當會導致一些非常奇怪的行為。 這只是為了好奇。

CPythonObjects/typeobject.c 中有關於這個主題的評論:

在 3.5 之前的 CPython 版本中, compatible_for_assignment中的代碼沒有設置為正確檢查內存布局/槽/等非 HEAPTYPE 類的兼容性,所以我們只是在任何不是 HEAPTYPE 的情況下禁止__class__分配 ->堆類型。

在 3.5 開發周期中,我們修復了compatible_for_assignment的代碼以正確檢查任意類型之間的兼容性,並開始允許在所有新舊類型實際上具有兼容插槽和內存布局的情況下進行__class__賦值(無論它們是否被實現)是否為 HEAPTYPE)。

然而,就在 3.5 發布之前,我們發現這導致了像 int 這樣的不可變類型的問題,其中解釋器假定它們是不可變的,並且會保留一些值。 以前這不是問題,因為它們確實是不可變的——特別是,解釋器應用這種實習技巧的所有類型碰巧也是靜態分配的,所以舊的 HEAPTYPE 規則“意外地”阻止了它們允許__class__賦值. 但是隨着__class__賦值的改變,我們開始允許像這樣的代碼

class MyInt(int): # ... # Modifies the type of *all* instances of 1 in the whole program, # including future instances (!), because the 1 object is interned. (1).__class__ = MyInt

(見https://bugs.python.org/issue24912 )。

理論上,正確的解決方法是確定哪些類依賴於這個不變量,並以某種方式禁止__class__只對它們進行賦值,也許通過一些機制,比如新的 Py_TPFLAGS_IMMUTABLE 標志(一種“黑名單”方法)。 但在實踐中,由於這個問題在 3.5 RC 周期的后期沒有被注意到,我們采取了保守的方法並恢復了我們曾經擁有的相同的 HEAPTYPE->HEAPTYPE 檢查,以及一個“白名單”。 目前,白名單僅包含 ModuleType 子類型,因為這些是首先激發補丁的情況——請參閱https://bugs.python.org/issue22986——並且由於模塊對象是可變的,我們可以確定他們絕對不會被拘留。 所以現在我們允許 HEAPTYPE->HEAPTYPEModuleType 子類型 -> ModuleType 子類型。

就我們所知,除了以下“if”語句之外的所有代碼都將正確處理非 HEAPTYPE 類,並且只需要 HEAPTYPE 檢查來保護解釋器已經烘焙的非 HEAPTYPE 類的子集所有實例都是真正不可變的。

解釋:

CPython以兩種方式存儲對象:

對象是在堆上分配的結構。 特殊規則適用於對象的使用,以確保它們被正確地垃圾收集。 對象永遠不會靜態分配或在堆棧上; 它們只能通過特殊的宏和函數訪問。 (類型對象是第一條規則的例外;標准類型由靜態初始化的類型對象表示,盡管 Python 2.2 的類型/類統一工作也使得擁有堆分配的類型對象成為可能)。

來自Include/object.h 中注釋的信息。

當您嘗試為some_obj.__class__設置新值時,將調用object_set_class函數。 它繼承自PyBaseObject_Type ,參見/* tp_getset */字段。 此函數檢查:新類型能否替換some_obj的舊類型?

舉個例子:

class A:
    pass

class B:
    pass

o = object()
a = A() 
b = B() 

第一種情況:

a.__class__ = B 

的類型的a目的是A ,堆型的,因為它是動態分配的。 以及B a的類型更改沒有問題。

第二種情況:

o.__class__ = B

o的類型是內置類型objectPyBaseObject_Type )。 它不是堆類型,因此TypeErrorTypeError

TypeError: __class__ assignment only supported for heap types or ModuleType subclasses.

您只能將__class__更改為具有相同內部 (C)布局的另一種類型。 運行時甚至不知道布局,除非類型本身是動態分配的(“堆類型”),因此這是排除作為源或目標的內置類型的必要條件。 您還必須具有相同名稱的同一組__slots__

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM