为什么不能更改对象实例的__class__属性?


10
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)

我知道这通常不是一个好主意,因为如果处理不当,可能会导致某些非常奇怪的行为。只是出于好奇。


3
内置类型出于效率的原因而牺牲了用户定义类型的动态性。注意,另一个可选的优化是插槽,这将同样防止这种情况。
— juanpa.arrivillaga 19/12/4

Answers:


6

CPython在Objects / typeobject.c中对此主题发表了评论:

在3.5之前的CPython版本中,compatible_for_assignment未设置其中的代码 来正确检查非HEAPTYPE类的内存布局/插槽等。因此,__class__在任何不是HEAPTYPE-> HEAPTYPE的情况下,我们都不允许分配。

在3.5开发周期中,我们修复了代码, compatible_for_assignment以正确检查任意类型之间的兼容性,并开始__class__在所有新旧类型确实具有兼容插槽和内存布局的情况下进行赋值(无论它们是否实现为HEAPTYPEs)或不)。

但是,在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-> HEAPTYPE 或 ModuleType子类型-> ModuleType子类型。

据我们所知,以下“ if”语句之后的所有代码都将正确处理非HEAPTYPE类,而HEAPTYPE检查仅用于保护解释器为之准备的非HEAPTYPE类的子集。所有实例都是真正不变的。

说明:

CPython以两种方式存储对象:

对象是在堆上分配的结构。特殊规则适用于对象的使用,以确保正确地对其进行垃圾回收。对象永远不会静态分配或在堆栈上分配;它们只能通过特殊的宏和函数进行访问。 (类型对象是第一个规则的例外;标准类型由静态初始化的类型对象表示,尽管对Python 2.2进行类型/类统一的工作也使得也可以具有堆分配的类型对象)。

来自Include / object.h中的注释的信息。

当您尝试将新值设置为时some_obj.__class__,将object_set_class调用该函数。它是从PyBaseObject_Type继承的,请参见/* tp_getset */field。此功能检查:新类型可以替换旧类型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是内置类型object(PyBaseObject_Type)。它不是堆类型,所以TypeError引发了:

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

4

您只能更改__class__为具有相同内部(C)布局的其他类型。除非类型本身是动态分配的(“堆类型”),否则运行时甚至不知道该布局,因此这是一个必需条件,必须排除内置类型作为源或目标。您还必须具有__slots__相同名称的相同集合。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.