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

时间:2019-07-04 00:08:04

标签: python types metaclass

免责声明: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中返回的类与实例不同,并且仍调用真实类中定义的实例方法。

1 个答案:

答案 0 :(得分:3)

否-__class__属性是有关在C API级别本身上“可见”的所有Python对象的布局的基本信息。这就是对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

元类的属性

尝试在__class__本身的元类上创建Foo描述符会很麻烦-type(Foo())repr(Foo())都将报告 instance < / em>为Bar,但“真实”对象类为Foo。从某种意义上讲,是的,它使type(Foo())处于撒谎状态,但不是您想的那样-type(Foo())将输出Bar()的代表,但它是Foo由于type.__call__内部的实施细节,导致混乱的repr:

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可用于举报错误的课程-该课程将对所有人有效 同一过程中的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

我在文档中没有找到一个窍门:在程序中访问__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

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