Ric*_*cco 10 python python-3.x
class A(object):
pass
class B(A):
pass
o = object()
a = A()
b = B()
Run Code Online (Sandbox Code Playgroud)
虽然我可以改变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)
Run Code Online (Sandbox Code Playgroud)
我知道这通常不是一个好主意,因为如果处理不当会导致一些非常奇怪的行为。这只是为了好奇。
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__赋值的改变,我们开始允许像这样的代码Run Code Online (Sandbox Code Playgroud)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 */字段。此函数检查:新类型可以替换旧类型some_obj吗?
举个例子:
class A:
pass
class B:
pass
o = object()
a = A()
b = B()
Run Code Online (Sandbox Code Playgroud)
第一种情况:
a.__class__ = B
Run Code Online (Sandbox Code Playgroud)
a对象的类型是A堆类型,因为它是动态分配的。以及B. 该a的类型是没有问题的改变。
第二种情况:
o.__class__ = B
Run Code Online (Sandbox Code Playgroud)
的类型o是内置类型object( PyBaseObject_Type)。它不是堆类型,因此TypeError引发了:
TypeError: __class__ assignment only supported for heap types or ModuleType subclasses.
Run Code Online (Sandbox Code Playgroud)
您只能更改__class__为具有相同内部 (C)布局的另一种类型。运行时甚至不知道布局,除非类型本身是动态分配的(“堆类型”),因此这是排除作为源或目标的内置类型的必要条件。您还必须具有__slots__相同名称的相同集合。