As you put it, and discovered in practice, A.method
is not on B's lookup chain - The relation of classes and metaclasses is not one of inheritance - it is one of 'instances' as a class is an instance of the metaclass.
Python is a fine language in which it behaves in expected ways, with very few surprises - and it is the same in this circumstances: If we were dealing with 'ordinary' objects, your situation would be the same as having an instance B
of the class A
. And B.method
would be present in B.__dict__
- a "super" call placed on a method defined for such an instance could never get to A.method
- it would actually yield an error. As B
is a class object, super
inside a method in B's __dict__
is meaningful - but it will search on B's __mro__
chain of classes (in this case, (object,)
) - and this is what you hit.
This situation should not happen often, and I don't even think it should happen at all; semantically it is very hard to exist any sense in a method that would be meaningful both as a metaclass method and as a method of the class itself. Moreover, if method
is not redefined in B
note it won't even be visible (nor callable) from B's instances.
Maybe your design should:
a. have a baseclass Base
, using your metaclass A
, that defines method
instead of having it defined in A
- and then define class B(Base):
instead
b. Or have the metaclass A
rather inject method
in each class
it creates, with code for that in it's __init__
or __new__
method - along:
def method(cls):
m = "this is an injected method of '%s'"
print(m % cls.__name__)
class A(type):
def __init__(cls, name, bases, dct):
setattr(cls, 'method', classmethod(method))
This would be my preferred approach - but it does not allow
one to override this method
in a class that uses this metaclass -
without some extra logic in there, the approach above would rather override any such method
explicit in the body.
The simpler thing is to have a base class Base
as above, or to inject a method with a different name, like base_method
on the final class, and hardcode calls to it in any overriding method
:
class B(metaclass=A):
@classmethod
def method(cls):
cls.base_method()
...
(Use an extra if
on the metaclass's __init__
so that the default method
is aliased to base_method
)
What you literally asked for begins here
Now, if you really has a use case for methods in the metaclass to be called from the class, the "one and obvious" way is to simply hardcode the call, as it was done before the existence of super
You can do either:
class B(metaclass=A):
@classmethod
def method(cls):
A.method(cls)
...
Which is less dynamic, but less magic and more readable - or:
class B(metaclass=A):
@classmethod
def method(cls):
cls.__class__.method(cls)
...
Which is more dynamic (the __class__
attribute works for cls
just like it would work in the case B
was just some instance of A
like my example in the second paragraph: B.__class__ is A
)
In both cases, you can guard yourself against calling a non existing method
in the metaclass with a simple if hasattr(cls.__class__, "method"): ...