是否可以使“type”的输出返回不同的类?

fla*_*kes 2 python types metaclass

所以免责声明:这个问题激起了我的好奇心,我问这个纯粹是出于教育目的。我想这对于 Python 专家来说是一个更大的挑战!

是否可以使type(foo)return 的输出与实际实例类不同?即它可以冒充冒名顶替者并通过诸如此类的检查吗type(Foo()) is Bar

@juanpa.arrivillaga提出了在实例上手动重新分配的建议__class__,但这会改变所有其他方法的调用方式。例如

class Foo:
    def test(self):
        return 1

class Bar:
    def test(self):
        return 2


foo = Foo()
foo.__class__ = Bar
print(type(foo) is Bar)
print(foo.test())

>>> True
>>> 2
Run Code Online (Sandbox Code Playgroud)

期望的输出是True, 1。即返回的类type与实例不同,并且仍然调用真实类中定义的实例方法。

jsb*_*eno 5

否 - 该__class__属性是有关 C API 级别本身“看到”的所有 Python 对象布局的基本信息。这就是通过调用来检查的内容type

这意味着:每个 Python 对象在其内存布局中都有一个槽,其中有一个用于指向 Python 对象(即该对象的类)的指针的空间。

即使您使用 ctypes 或其他方式覆盖对该槽的保护并从 Python 代码更改它(因为修改obj.__class__with=在 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
Run Code Online (Sandbox Code Playgroud)

元类的属性

尝试 在其自身__class__的元类上创建描述符Foo会很混乱 - 和type(Foo())都会repr(Foo())报告 的实例Bar但“真正的”对象类将是 Foo。从某种意义上说,是的,它是type(Foo())谎言,但不是以你想象的方式 - type(Foo()) 将输出 的 repr Bar(),但Foo由于内部的实现细节,它的 repr 被搞乱了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
Run Code Online (Sandbox Code Playgroud)

修改type自身

由于没有人type从任何地方“导入”,并且只使用内置类型本身,因此可以对内置可 type调用对象进行猴子补丁以报告错误类 - 并且它将适用于同一进程中的所有 Python 代码,依赖于对type:

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

Run Code Online (Sandbox Code Playgroud)

这里有一个我在文档中没有找到的技巧:__builtins__在程序中访问时,它充当字典。然而,在交互式环境中,例如 Python 的 Repl 或 Ipython,它是一个模块 - 检索原始版本type并编写修改版本,必须__builtins__ 考虑到这一点 - 上面的代码是双向的。

并对此进行测试(我从磁盘上的 .py 文件导入了上面的代码片段):

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

Run Code Online (Sandbox Code Playgroud)

尽管这适用于演示目的,但替换内置类型会导致“不和谐”,这在更复杂的环境(例如 IPython)中被证明是致命的:如果运行上面的代码片段,Ipython 将崩溃并立即终止。