Suppose we are implementing a metaclass that needs to know the method resolution order before the class is instantiated.
class Meta(type):
def __new__(cls, name, bases, namespace):
mro = ...
Is there a builtin way to compute the mro, that is a way other than reimplementing the C3 algorithm?
The simpler thing is to just create a temporary class, extract its __mro__
, compute your things, and then create the metaclass for real:
class Meta(type):
def __new__(metacls, name, bases, namespace):
tmp_cls = super().__new__(metacls, name, bases, namespace)
mro = tmp_cls.__mro__
del tmp_cls # Not actually needed, just to show you are done with it.
...
# do stuff
...
new_class = super().__new__(metacls, name, bases, namespace)
...
return new_class
Supposing that can't be done, due to crazy side-effects on the metaclasses of some of the super-classes on the hierarchy - then the same idea, but cloning the classes in the bases to "stub" classes before doing it - but possibly, reimplementing the C3 algorithm is easier than that - and certainly more efficient, since for each class you'd create an N ** 2 number of stub superclasses, where N is the depth of your class hierarchy (well that could be cached should you pick this route).
Anyway, the code for that could be something along:
stub_cache = {object: object}
def get_stub_class(cls):
# yields an mro-equivalent with no metaclass side-effects.
if cls is object:
return object
stub_bases = []
for base in cls.__bases__:
stub_bases.append(get_stub_class(base))
if cls not in stub_cache:
stub_cache[cls] = type(cls.__name__, tuple(stub_bases), {})
return stub_cache[cls]
def get_future_mro(name, bases):
stub_bases = tuple(get_stub_class(base) for base in bases)
stub_cls = type(name, stub_bases, {})
reversed_cache = {value:key for key, value in stub_cache.items()}
return [reversed_cache[mro_base] for mro_base in stub_cls.__mro__[1:]]
class Meta(type):
def __new__(metacls, name, bases, namespace):
mro = get_future_mro(name, bases)
print(mro)
return super().__new__(metacls, name, bases, namespace)
(This thing works for basic cases I've tried in interactive mode - but there might be complicated edge cases not covered, with multiple metaclasses and such)
It turns out the functools
module has a _c3_merge
function that implements the C3 linearization algorithm:
import functools
def compute_mro(*bases):
return functools._c3_merge(base.mro() for base in bases)
# Example:
class F: pass
class E: pass
class D: pass
class C(D,F): pass
class B(D,E): pass
class A(B,C): pass
print(compute_mro(B, C)) # [B, C, D, E, F, object]
print(A.mro()) # [A, B, C, D, E, F, object]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With