Say I have built a library containing a Foo
class, with support for some magic methods, say __add__()
and __radd__()
:
>>> class Foo(object):
... def __add__(self, rhs):
... print("Foo.__add__", rhs)
... def __radd__(self, lhs):
... print("Foo.__radd__", lhs)
...
>>> foo = Foo()
>>> foo + 3
Foo.__add__ 3
>>> 3 + foo
Foo.__radd__ 3
When computing 3 + foo
, python first calls type(3).__add__(3, foo)
, but as this returns NotImplemented
, it falls back to type(foo).__radd__(foo, 3)
:
>>> type(3).__add__(3, foo)
NotImplemented
I would like developers to be able to build libraries on top of my library, say a library containing a class Bar
, and I want them to have full control. In particular, I want to implement some mechanism that lets the other library decide whether foo + bar
should call foo.__add__(bar)
or bar.__radd__(foo)
.
I see that NumPy solved this using the __array_priority__
scheme. But this seems to cause some headaches (considering the number of questions and issues opened about this). Are there other best practices?
A special magic method in Python allows instances of your classes to behave as if they were functions, so that you can "call" them, pass them to functions that take functions as arguments, and so on.
Magic methods are most frequently used to define overloaded behaviours of predefined operators in Python. For instance, arithmetic operators by default operate upon numeric operands. This means that numeric objects must be used along with operators like +, -, *, /, etc.
Python provides the operator x += y to add two objects in-place by calculating the sum x + y and assigning the result to the first operands variable name x . You can set up the in-place addition behavior for your own class by overriding the magic “dunder” method __iadd__(self, other) in your class definition.
Such methods are called special or magic methods. One such method is __dir__() which is internally called by dir() built-in function. Naturally, these attributes are inherited by all Python classes.
A simple option is to try to let the LHS do whatever it needs to do (in the example below it calls the RHS's value()
method) and in case it raises an exception, catch it and return NotImplemented
:
class Foo(object):
[...]
def __add__(self, rhs):
try:
return self._value + rhs.value()
except AttributeError:
return NotImplemented
Simple as can be, no need to maintain a list of SUPPORTED_TYPES
. However, there is a risk that the RHS implements a value()
method that has nothing to do with this task, so it might be a bit risky. Moreover, there is no easy way for the rhs
to get full control over the result.
In Python, it's usually better to beg for forgiveness rather than to ask for permission, as above, but you may prefer to check that the rhs
has the value()
method:
class Foo(object):
def __add__(self, rhs):
rhs_value_func = getattr(rhs, "value", None)
if rhs_value_func is None:
return NotImplemented
else:
return self._value + rhs_value_func()
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