1

In my classes, I often use overloading like in this site to define their behavior when using operators like +,-, etc. by overwriting the __add__,__sub__, etc. methods.

An example would be this:

class MyClass:    
    def __add__(self, other):
        result = my_custom_adder(self, other)
        return self.__class__(result)

Is there any way to define the behavior when chaining of such overwritten operators?

For example, an addition of three elements a + b + c of a complex class could be implemented much more efficiently by taking all three elements into account at once and not just calculating (a + b) + c sequentially.

Of cause I can just introduce a classmethod:

class MyClass:    
    def __add__(self, other):
        return self.my_custom_adder(self, other)

    @classmethod
    def my_custom_adder(cls, *args):
        result = do_efficient_addition(*args)
        return cls(result)

Then, I can call my_custom_adder(a, b, c) instead. But this requires the user to know that there is a method like this and calling it explicitly, instead of just using a + b + c.

eaglesear
  • 480
  • 4
  • 12
  • Not possible. `+` is a binary operator (i.e. it takes 2 arguments) and there's nothing you can do about it. – Aran-Fey May 02 '18 at 16:24
  • Thanks Aran-Fey. My question was a bit more general than the one you marked this as a duplicate of, but the answer fits nevertheless. – eaglesear May 02 '18 at 16:34

2 Answers2

1

No, there isn't really anything for such hook methods to know that they are part of a larger equation.

What you could do is produce an intermediate result object, one that collects the objects and only when you need the outcome do the actual calculation.

For example, if your objects are integer-like, and implements the __int__ method to facilitate conversions, an intermediary value could postpone the calculation until then:

class BaseIntValue(object):
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        attrs = ', '.join([f"{k}={v!r}" for k, v in vars(self).items()])
        return f'{type(self).__name__}({attrs})'
    def __int__(self):
        return int(self.value)
    def __add__(self, other):
        if not isinstance(other, BaseIntValue):
            return NotImplemented
        return IntermediarySummedIntValue(self, other)

class IntermediarySummedIntValue(BaseIntValue):
    def __init__(self, *values):
        self.values = values
    def __int__(self):
        print("Expensive calculation taking place")
        return sum(map(int, self.values))
    def __add__(self, other):
        if not isinstance(other, BaseIntValue):
            return NotImplemented
        if isinstance(other, IntermediarySummedIntValue):
            values = self.values + other.values
        else:
            values = self.values + (other,)
        return IntermediarySummedIntValue(*values)

Demo:

>>> BaseIntValue(42) + BaseIntValue(81) + BaseIntValue(314)
IntermediarySummedIntValue(values=(BaseIntValue(value=42), BaseIntValue(value=81), BaseIntValue(value=314)))
>>> int(BaseIntValue(42) + BaseIntValue(81) + BaseIntValue(314))
Expensive calculation taking place
437
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
0

+, -, etc... are binary operators.

>>> tree=ast.parse('a+b+c')
>>> astpretty.pprint(tree)
Module(
    body=[
        Expr(
            lineno=1,
            col_offset=0,
            value=BinOp(
                lineno=1,
                col_offset=3,
                left=BinOp(
                    lineno=1,
                    col_offset=0,
                    left=Name(lineno=1, col_offset=0, id='a', ctx=Load()),
                    op=Add(),
                    right=Name(lineno=1, col_offset=2, id='b', ctx=Load()),
                ),
                op=Add(),
                right=Name(lineno=1, col_offset=4, id='c', ctx=Load()),
            ),
        ),
    ],
)

So, no, there is no way to override those operators to get what you want.

Go with the static method.

fferri
  • 18,285
  • 5
  • 46
  • 95