I defined a metaclass which add a method named "test" to the created classes:
class FooMeta(type):
def __new__(mcls, name, bases, attrs):
def test(self):
return super().test()
attrs["test"] = test
cls = type.__new__(mcls, name, bases, attrs)
return cls
Then I create two classes using this Metaclass
class A(metaclass=FooMeta):
pass
class B(A):
pass
When I run
a = A()
a.test()
a TypeError is raised at super().test()
:
super(type, obj): obj must be an instance or subtype of type
Which means super()
cannot infer the parent class correctly. If I change the super
call into
def __new__(mcls, name, bases, attrs):
def test(self):
return super(cls, self).test()
attrs["test"] = test
cls = type.__new__(mcls, name, bases, attrs)
return cls
then the raised error becomes:
AttributeError: 'super' object has no attribute 'test'
which is expected as the parent of A
does not implement test
method.
So my question is what is the correct way to call super()
in a dynamically added method? Should I always write super(cls, self)
in this case? If so, it is too ugly (for python3)!
Parameterless super()
is very special in Python because it triggers some behavior during code compilation time itself: Python creates an invisible __class__
variable which is a reference to the "physical" class
statement body were the super()
call is embedded (it also happens if one makes direct use of the __class__
variable inside a class method).
In this case, the "physical" class where super()
is called is the metaclass FooMeta
itself, not the class it is creating.
The workaround for that is to use the version of super
which takes 2 positional arguments: the class in which it will search the immediate superclass, and the instance itself.
In Python 2 and other occasions one may prefer the parameterized use of super
, it is normal to use the class name itself as the first parameter: at runtime, this name will be available as a global variable in the current module. That is, if class A
would be statically coded in the source file, with a def test(...):
method, you would use super(A, self).test(...)
inside its body.
However, although the class name won't be available as a variable in the module defining the metaclass, you really need to pass a reference to the class as the first argument to super
. Since the (test) method receives self
as a reference to the instance, its class is given by either self.__class__
or type(self)
.
TL;DR: just change the super call in your dynamic method to read:
class FooMeta(type):
def __new__(mcls, name, bases, attrs):
def test(self):
return super(type(self), self).test()
attrs["test"] = test
cls = type.__new__(mcls, name, bases, attrs)
return cls