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

时间:2019-12-04 11:42:06

标签: python python-3.x

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)

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

2 个答案:

答案 0 :(得分:6)

CPython Objects/typeobject.c中对此主题有评论:

  

在CPython 3.5之前的版本中,   compatible_for_assignment未设置为正确检查内存   非HEAPTYPE类的layout / slot / etc。兼容性,因此我们   在任何不是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-> HEAPTYPE or   ModuleType子类型-> ModuleType子类型。

     

据我们所知,以下“ if”语句之后的所有代码   将正确处理非HEAPTYPE类,并且HEAPTYPE检查为   只需要保护非HEAPTYPE类的子集即可   假设所有实例都是   真正的一成不变。

说明:

CPython 以两种方式存储对象:

  

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

Include/object.h中评论的信息。

当您尝试将新值设置为some_obj.__class__时,将调用object_set_class函数。它是从PyBaseObject_Type继承的,请参见/* tp_getset */字段。函数checks:新类型可以替换some_obj中的旧类型吗?

以您的示例为例:

class A:
    pass

class B:
    pass

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

第一种情况:

a.__class__ = B 

a对象的类型是A(堆类型),因为它是动态分配的。以及Ba的类型更改没有问题。

第二种情况:

o.__class__ = B

o的类型是内置类型objectPyBaseObject_Type)。它不是堆类型,因此引发TypeError

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

答案 1 :(得分:4)

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