As stated in the very appropriate comment above:
"""
You can't, not in Python. You might be able to with a C extension, but Python uses bootstrapping to set up that dependency, which involves mutating internal data that isn't, under normal circumstances, meant to be mutated. –
Silvio Mayolo
""".
Well, that, while correct, is not entirely true - Python dynamicism allows one to change, in a limited form, classes bases and inheritance, and even object classes, after a class or object is created.
That means that part of the same bootstrapping can be performed in pure Python code.
There is a second hard-coded relationship in Python that you don't mention in your question: type
is own metaclass - that is type
is an instance of type
: that really can't be emulated in pure Python code. The mutual relationship of type
and object
however, can. Again, that is of no practical use whatsoever, and object
and type
are always kept in the loop as baseclass and base metaclass: you can't really do anything without them.
That said, check the examples bellow.
First thing is that type
is quite unique, not due to this relationship with object
, but because it does all the job for actually creating a class in Python under the hood - and that is a lot of things. Eventually all custom metaclasses have at some point to call type.__new__
to actually create a type - so while your MyType
can be made to inherit from MyObject
, it will also have to retain inheritance from type
(as one of the bases) - so that class creation job can be done. (ok - eventually, there is a way out of it, with overriding the right methods, and proxying calls to type
- but lets deffer that).
Fact is: to gain this egg-hen relation ship you have to change at least one of your objects after it is created.
It is not possible to modify a class` class (its metaclass) after it is created:
class A: pass
class M(A, type): pass
A.__class__ = M
Will raise TypeError: __class__ assignment only supported for mutable types or ModuleType subclasses
. But the converse is actually possible:
class M(type): pass
class A(metaclass=M): pass
M.__bases__ = (A, type)
As I wrote above, one needs to keep type
as one of the bases for M - trying to remove it from the bases in the assignment, will raise: TypeError: __bases__ assignment: 'A' object layout differs from 'type'
. But doing M.__bases__ = (A, type)
works, and after doing that:
In [18]: isinstance(A, M)
Out[18]: True
In [19]: issubclass(M, A)
Out[19]: True
Now, for the second part, if you don't want M
to inherit from type
at all, just implementing __new__
and __call__
on M
suffices for having a "working metaclass".
HOWEVER, there is no way then to pass M
as the first parameter in the call to type.__new__
: Python checks internally that the metaclass passed in this call is a "real" subclass of type
. This means that when you ask for the class of A
subsequently, it won't say it is a "class of M" using normal methods - but then, we can "falsify" that somewhat, by implementing the __instancecheck__
special method in the "metaclass of the metaclass".
Note that due to some of the restrictions in __bases__
assignmen, and to the fact that __instancecheck__
must live on the metahclass of the class whose instance we want to check (so, in the metaclass of our metaclass), we need 2 other intermediate classes now.
The code in __instancecheck__
and __subclasscheck__
bellow can be tweaked to perform some "real checking" - just returning "True" is enough for demonstration purposes:
class A: pass
class M(type):
def __instancecheck__(self, instance):
return True
def __subclasscheck__(self, subclass):
return True
class N(A, metaclass=M):
def __new__(mcls, name, bases, ns):
cls = type.__new__(type, name, bases, ns) # <- here, M can't be the first argument, as it does not inherit from "type"
return cls
def __call__(cls, *args, **kw):
instance = cls.__new__(cls, *args, **kw)
if isinstance(instance, cls):
instance.__init__(*args, **kw)
return instance
class B(metaclass=N): pass
N.__bases__ = (N,)
And now, with a metaclass not inheriting from type, you can have:
In [42]: isinstance(B, N)
Out[42]: True
In [43]: issubclass(N, B)
Out[43]: True
However, if the test is made through type
you won't see N
as the metaclass:
In [44]: type(B)
Out[44]: type