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"): ...