Are there better methods for extensible magic methods in python?

Let's say I built a library containing the Foo class with support for some magic methods, for example __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 evaluating 3 + foo , python first calls type(3).__add__(3, foo) , but since this returns NotImplemented , it returns to type(foo).__radd__(foo, 3) :

 >>> type(3).__add__(3, foo) NotImplemented 

I would like for developers to be able to build libraries on top of my library, say, a library containing the Bar class, and I want them to have full control. In particular, I want to implement some mechanism that allows another library to decide whether foo + bar call foo.__add__(bar) or bar.__radd__(foo) .

I see that NumPy solved this with the __array_priority__ schema. But this, apparently, causes some headaches (given the number of questions and problems open about this). Are there any other recommendations?

+6
source share
3 answers

One popular option is to maintain a list of types supported by LHS, and if the RHS type is not listed, return NotImplemented :

 class Foo(object): SUPPORTED_TYPES = (int, Foo) def __add__(self, rhs): if isinstance(type(rhs), SUPPORTED_TYPES): [...] # compute self + rhs else: return NotImplemented 

This works well if rhs not an intellectual subtype of one of SUPPORTED_TYPES : it cannot gain control. Moreover, listing types are not very flexible. It might be better to rely on duck print than a list of hard-coded types.

+1
source

A simple option is to try to let LHS do whatever it needs (in the example below, it calls the RHS value() method), and if an exception occurs, it excludes it and returns NotImplemented :

 class Foo(object): [...] def __add__(self, rhs): try: return self._value + rhs.value() except AttributeError: return NotImplemented 

Simple as it may be, there is no need to maintain a SUPPORTED_TYPES list. However, there is a risk that RHS implements the value() method, which has nothing to do with this task, so it can be a little risky. Moreover, for rhs there is no easy way to get full control over the result.

In Python, it is usually better to ask forgiveness rather than asking for permission, as mentioned above, but you may prefer to check that rhs has a 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() 
+1
source

Another option is to use an attribute like __foo_priority__ , somewhat similar to NumPy with its __array_priority__ :

 class Foo(object): __foo_priority__ = 0 def __add__(self, rhs): delegate = True try: rhs_prio = type(rhs).__foo_priority__ delegate = (self.__foo_priority__ < rhs_prio) except AttributeError: delegate = True if delegate: return NotImplemented else: return self.value_ + rhs.value() 

This option is a bit more complicated, but quite flexible. The only (secondary) problem with this option is that the rhs type requires an additional attribute, so there is no way to give rhs control if it is an existing type without this attribute.

0
source

Source: https://habr.com/ru/post/1015581/


All Articles