Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there best practices for extensible magic methods in python?

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?

like image 928
MiniQuark Avatar asked Mar 08 '17 08:03

MiniQuark


People also ask

Which situation is made possible by the Python magic method?

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.

Why we use magic methods in Python?

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.

How do you add magic methods in Python?

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.

Are magic methods inherited Python?

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.


1 Answers

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()
like image 68
MiniQuark Avatar answered Oct 19 '22 13:10

MiniQuark