问题
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!
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 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
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.
回答1:
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
.
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.
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.
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.
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
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
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
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.
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
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.
来源:https://stackoverflow.com/questions/56879245/is-it-possible-to-make-the-output-of-type-return-a-different-class