否 - __class__ 属性是有关所有 Python 对象布局的基本信息,如在 C API 级别本身“可见”。这就是调用type 所检查的内容。
这意味着:每个 Python 对象在其内存布局中都有一个插槽,其中包含单个指针的空间,指向作为该对象类的 Python 对象。
即使您使用 ctypes 或其他方式来覆盖对该插槽的保护并从 Python 代码中更改它(因为使用 = 修改 obj.__class__ 在 C 级别受到保护),更改它会有效地更改对象类型: __class__ 插槽中的值是对象的类,在您的示例中,test 方法将从那里的类(Bar)中选取。
但是这里有更多信息:在所有文档中,type(obj) 被视为等同于 obj.__class__ - 但是,如果对象的类定义了名称为 __class__ 的描述符,则在使用形成obj.__class__。 type(obj) 但是会直接检查实例的__class__ 槽并返回真正的类。
所以,这可以“欺骗”使用obj.__class__ 的代码,但不能使用type(obj):
class Bar:
def test(self):
return 2
class Foo:
def test(self):
return 1
@property
def __class__(self):
return Bar
元类的属性
试图在Foo 的元类上创建__class__ 描述符本身会很麻烦——type(Foo()) 和repr(Foo()) 都会报告Bar 的实例,但“真正的”对象类将是 Foo。从某种意义上说,是的,它使type(Foo()) 撒谎,但不是你想的那样——type(Foo()) 会输出Bar() 的repr,但搞砸的是Foo 的repr up,由于type.__call__内部的实现细节:
In [73]: class M(type):
...: @property
...: def __class__(cls):
...: return Bar
...:
In [74]: class Foo(metaclass=M):
...: def test(self):
...: return 1
...:
In [75]: type(Foo())
Out[75]: <__main__.Bar at 0x55665b000578>
In [76]: type(Foo()) is Bar
Out[76]: False
In [77]: type(Foo()) is Foo
Out[77]: True
In [78]: Foo
Out[78]: <__main__.Bar at 0x55665b000578>
In [79]: Foo().test()
Out[79]: 1
In [80]: Bar().test()
Out[80]: 2
In [81]: type(Foo())().test()
Out[81]: 1
修改type本身
由于没有人从任何地方“导入”type,因此只需使用
内置类型本身,可以猴子补丁内置
type 可调用以报告虚假课程 - 它适用于所有人
依赖于type调用的同一进程中的Python代码:
original_type = __builtins__["type"] if isinstance("__builtins__", dict) else __builtins__.type
def type(obj_or_name, bases=None, attrs=None, **kwargs):
if bases is not None:
return original_type(obj_or_name, bases, attrs, **kwargs)
if hasattr(obj_or_name, "__fakeclass__"):
return getattr(obj_or_name, "__fakeclass__")
return original_type(obj_or_name)
if isinstance(__builtins__, dict):
__builtins__["type"] = type
else:
__builtins__.type = type
del type
这里有一个我在文档中没有找到的技巧:在程序中访问__builtins__ 时,它就像字典一样工作。但是,在 Python 的 Repl 或 Ipython 等交互环境中,它是一个
模块 - 检索原始 type 并写入修改后的
__builtins__ 的版本必须考虑到这一点 - 上面的代码
双向工作。
并对此进行测试(我从磁盘上的 .py 文件导入了上面的 sn-p):
>>> class Bar:
... def test(self):
... return 2
...
>>> class Foo:
... def test(self):
... return 1
... __fakeclass__ = Bar
...
>>> type(Foo())
<class '__main__.Bar'>
>>>
>>> Foo().__class__
<class '__main__.Foo'>
>>> Foo().test()
1
虽然这用于演示目的,但替换内置类型会导致“不和谐”,这在 IPython 等更复杂的环境中被证明是致命的:如果运行上面的 sn-p,Ipython 将立即崩溃并终止。