I try to use metaclass to implement numeric magic methods for several classes, then override some of them in the "class instance".
from abc import ABCMeta
class Meta(ABCMeta):
def __add__(self, other):
print('meta')
class C(metaclass=Meta):
@classmethod
def __add__(cls, other):
print('C')
C + 3 # meta
C.__add__(3) # C
It shows that the +
operator is directly called with Meta
's __add__
, but C.__add__
calls getattr(C, '__add__')
and correctly use the overriden class method.
I've tried several code pieces to confirm that.
class Meta(ABCMeta):
def call(self):
print('meta')
class C(metaclass=Meta):
@classmethod
def call(cls):
print('C')
C.call() # C
class C:
@classmethod
def __add__(cls, other):
print('C')
C + 3 # TypeError
C.__add__(3) # C
I guess something special happends when I "register" my implmentation of __add__
, so the decorator @classmethod
does not work.
So, how does python evaluates the expression C + 3
? If I want to override __add__
for C
, is there any solution other than inherit another metaclass from Meta
?
So, how does python evaluates the expression C + 3?
What takes place here is that Python's method and attribute search priority are different from ordinary methods (like call
), and methods that are intended to be called indirectly by the language, through the use of an operator (like __add__
)
For the later, Python defers directly to the slot in the class of the instance being added. That is: When you use "C" with the +
operator, its class is "Meta", and Meta.__add__
is called.
In an ordinary attribute access using the .
notation (either C.call
or even C.__add__
(insteaf of using +
)), the normal rules for attribute access - which are implemented in object.__getattribute__
are followed - I've summarized the precedence in this answer. But in short: for dotted access, the classmethod in the class "instance" is retrieved before the method defined in the "class'class" (just as would happen for an attribute in the __dict__
of an "ordinary instance" compared to the same attribute in its class).
As for: "If I want to override __add__
for C, is there any solution other than inherit another metaclass from Meta?"
The "obvious way" would be to have a separate metaclass for each class you want to implement a "class __add__
" method to. However, you can write Meta.__add__
so that it, upon being called, check for the existence of an __add__
method in the class, and forwards the execution of the opertation to it.
from abc import ABCMeta
class Meta(ABCMeta):
def __add__(cls, other):
print('meta')
cls_add = getattr(cls, "__add__", None)
if not cls_add:
return NotImplemented
# checks for __add__ both as an ordinary and as a classmethod
if (unbound:= getattr(method, "__func__", None)) is not None and unbound is __class__.__add__ :
# getattr returned an __add__ method from the metaclass (this very same method here)
raise NotImplemented
if unbound: # if this is not None, __add__ is a class method
return method(other)
return method(cls, other) # __add__ is an ordinary method, and we have to fill the 'self' parameter with cls