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

Posted

技术标签:

【中文标题】是否可以让“type”的输出返回不同的类?【英文标题】:Is it possible to make the output of `type` return a different class? 【发布时间】:2019-11-14 16:22:37 【问题描述】:

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

是否可以让type(foo) 的输出返回与实际实例类不同的值?即它可以冒充冒名顶替者并通过诸如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

所需的输出将是True1。即type中返回的类与实例不同,实际类中定义的实例方法仍然被调用。

【问题讨论】:

您只想为特定实例foo 执行此操作,还是为整个Foo 类执行此操作? 我越想这个,我就越觉得不可行。您可以通过使用抽象基类和__subclasscheck__ 创建一个模拟类来模拟使用isinstance。但使用typeis.... 不确定 @Barmar 可能只是一个例子。我很好奇特定实例是否可以操纵从type 返回的结果。 @juanpa.arrivillaga 我也是。可能会开始深入研究type 方法的实际实现,以证明它是否不可能。 【参考方案1】:

否 - __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 将立即崩溃并终止。

【讨论】:

这真是太有见地了!非常感谢您的帖子!

以上是关于是否可以让“type”的输出返回不同的类?的主要内容,如果未能解决你的问题,请参考以下文章

没有成员函数的类实例的默认返回

静态工厂的第四个优点是返回对象的类可以根据输入参数的不同而不同。

Type.GetType()反射另外项目中的类时返回null的解决方法

Type.GetType()反射另外项目中的类时返回null的解决方法

运行查询时是否可以跳过列?

python中怎么让类返回值?