Is It Possible To Make The Output Of `type` Return A Different Class?
Solution 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)
:
classBar:deftest(self):
return2classFoo:deftest(self):
return1@propertydef__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"] ifisinstance("__builtins__", dict) else __builtins__.typedeftype(obj_or_name, bases=None, attrs=None, **kwargs):
if bases isnotNone:
return original_type(obj_or_name, bases, attrs, **kwargs)
ifhasattr(obj_or_name, "__fakeclass__"):
returngetattr(obj_or_name, "__fakeclass__")
return original_type(obj_or_name)
ifisinstance(__builtins__, dict):
__builtins__["type"] = typeelse:
__builtins__.type = typedeltype
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):
>>>classBar:...deftest(self):...return2...>>>classFoo:...deftest(self):...return1... __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.
Post a Comment for "Is It Possible To Make The Output Of `type` Return A Different Class?"