是否可以使`type`的输出返回不同的类? [英] Is it possible to make the output of `type` return a different class?
问题描述
因此免责声明:此问题引起了我的好奇心,而我出于纯粹的教育目的而提出这个要求.我想,这里的Python专家还面临更多挑战!
So disclaimer: this question has piqued my curiosity a bit, and I'm asking this for purely educational purposes. More of a challenge for the Python gurus here I suppose!
是否可以使type(foo)
的输出返回与实际实例类不同的值?即它可以冒充冒名顶替者并通过诸如type(Foo()) is Bar
的支票吗?
Is it possible to make the output of type(foo)
return a different value than the actual instance class? i.e. can it pose as an imposter and pass a check such as type(Foo()) is Bar
?
@ juanpa.arrivillaga 建议在实例上手动重新分配__class__
,但是具有更改所有其他方法的调用方式的效果.例如
@juanpa.arrivillaga made a suggestion of manually re-assigning __class__
on the instance, but that has the effect of changing how all other methods would be called. e.g.
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
所需的输出将是True
,1
.即,type
中返回的类与实例不同,并且仍然调用真实类中定义的实例方法.
The desired outputs would be True
, 1
. i.e The class returned in type
is different than the instance, and the instance methods defined in the real class still get invoked.
推荐答案
否-__class__
属性是有关在C API级别本身上可见"的所有Python对象的布局的基本信息.这就是通过调用type
检查的内容.
No - the __class__
attribute is a fundamental information on the layout of all Python objects as "seen" on the C API level itself. And that is what is checked by the call to type
.
这意味着:每个Python对象在其内存布局中都有一个插槽,该插槽中有一个指向单个指针的空间,该指针指向该对象的类的Python对象.
That means: every Python object have a slot in its in-memory layout with space for a single pointer, to the Python object that is that object's class.
即使您使用ctypes或其他方式来覆盖对该插槽的保护并从Python代码更改它(由于在C级别上保护了使用=
修改obj.__class__
),对其进行更改也可以有效地更改对象类型: __class__
插槽中的值是对象的类,在您的示例中,将从该类(Bar)中的类中选择test
方法.
Even if you use ctypes or other means to override protection to that slot and change it from Python code (since modifying obj.__class__
with =
is guarded at the C level), changing it effectively changes the object type: the value in the __class__
slot IS the object's class, and the test
method would be picked from the class in there (Bar) in your example.
但是这里有更多信息:在所有文档中,type(obj)
被视为等同于obj.__class__
-但是,如果对象的类定义了名称为__class__
的描述符,则在使用表格obj.__class__
. type(obj)
但是将直接检查实例的__class__
插槽并返回真实的类.
However there is more information here: in all documentation, type(obj)
is regarded as equivalent as obj.__class__
- however, if the objects'class defines a descriptor with the name __class__
, it is used when one uses the form obj.__class__
. type(obj)
however will check the instance's __class__
slot directly and return the true class.
因此,这可以撒谎"使用obj.__class__
进行编码,而不能使用type(obj)
:
So, this can "lie" to code using obj.__class__
, but not 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())
都将报告type(Foo())
说谎,但不是您想的那样-type(Foo())将输出Bar()
的repr,但是Foo
的repr却被弄乱了由于type.__call__
中的实现细节:
Property on the metaclass
Trying to mess with creating a __class__
descriptor on the metaclass of Foo
itself will be messy -- both type(Foo())
and repr(Foo())
will report an instance of Bar
, but the "real" object class will be Foo. In a sense, yes, it makes type(Foo())
lie, but not in the way you were thinking about - type(Foo()) will output the repr of Bar()
, but it is Foo
's repr that is messed up, due to implementation details inside 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
可调用以报告错误的类-它将对所有人有效
相同过程中的Python代码依赖于对type
的调用:
Modifying type
itself
Since no one "imports" type
from anywhere, and just use
the built-in type itself, it is possible to monkeypatch the builtin
type
callable to report a false class - and it will work for all
Python code in the same process relying on the call to 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__
的版本必须考虑到这一点-上面的代码
双向工作.
There is one trick here I had not find in the docs: when acessing __builtins__
in a program, it works as a dictionary. However, in an interactive environment such as Python's Repl or Ipython, it is a
module - retrieving the original type
and writting the modified
version to __builtins__
have to take that into account - the code above
works both ways.
并对此进行测试(我从磁盘上的.py文件导入了上述代码段):
And testing this (I imported the snippet above from a .py file on disk):
>>> 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将崩溃并立即终止.
Although this works for demonstration purposes, replacing the built-in type caused "dissonances" that proved fatal in a more complex environment such as IPython: Ipython will crash and terminate immediately if the snippet above is run.
这篇关于是否可以使`type`的输出返回不同的类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!