# 11.9. Operator Arithmetic Right¶

• - - rsub

• * - rmul

• / - rtruediv

• // - rfloordiv

• ** - rpow

• % - rmod

• @ - rmatmul

## 11.9.1. SetUp¶

>>> from dataclasses import dataclass
>>> from functools import reduce

Operator

Method

obj + other

obj - other

obj.__rsub__(other)

obj * other

obj.__rmul__(other)

obj / other

obj.__rtruediv__(other)

obj // other

obj.__rfloordiv__(other)

obj ** other

obj.__rpow__(other)

obj % other

obj.__rmod__(other)

obj @ other

obj.__rmatmul__(other)

## 11.9.3. Example¶

>>> class Vector:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __radd__(self, other): ...              # x + y     if fails, then calls y.__radd__(x)
...     def __rsub__(self, other): ...              # x - y     if fails, then calls y.__rsub__(x)
...     def __rmul__(self, other): ...              # x * y     if fails, then calls y.__rmul__(x)
...     def __rpow__(self, power, modulo=None): ... # x ** y    if fails, then calls y.__rpow__(x)
...     def __rmatmul__(self, other): ...           # x @ y     if fails, then calls y.__rmatmul__(x)
...     def __rtruediv__(self, other): ...          # x / y     if fails, then calls y.__rtruediv__(x)
...     def __rfloordiv__(self, other): ...         # x // y    if fails, then calls y.__rfloordiv__(x)
...     def __rmod__(self, other): ...              # x % y     if fails, then calls y.__rmod__(x)

## 11.9.4. Left Operation¶

>>> @dataclass
... class Vector:
...     x: int
...     y: int
...
...         new_x = self.x + other.x
...         new_y = self.y + other.y
...         return Vector(new_x, new_y)
...
>>>
>>>
>>> a = Vector(x=1, y=2)
>>> b = Vector(x=3, y=4)
>>> c = Vector(x=5, y=6)
>>>
>>> (a+b) + c
Vector(x=9, y=12)

## 11.9.5. Left Operation¶

>>> class Left:
...         return 'left'
>>>
>>> class Right:
...     pass

Left operation:

>>> Left() + Right()   # left.__add__(right)
'left'

What if Right class does not define __add__ attribute?

>>> Right() + Left()    # right.__add__(left)
Traceback (most recent call last):
TypeError: unsupported operand type(s) for +: 'Right' and 'Left'

## 11.9.6. Right Operation¶

>>> class Left:
...         return 'left'
...
...         return 'left too'
>>>
>>>
>>> class Right:
...     pass

Left operation:

>>> Left() + Right()   # left.__add__(right)
'left'

What if Right class does not define __add__ attribute? Python will search for __radd__ attribute in Right class:

'left too'

## 11.9.7. Both¶

>>> class Left:
...         return 'left'
...
...         return 'left too'
>>>
>>>
>>> class Right:
...         return 'right'
>>>
>>>
>>> Left() + Right()    # left.__add__(right)
'left'
>>>
>>> Right() + Left()    # right.__add__(left)
'right'

## 11.9.8. Example¶

>>> a = 1
>>> b = 2
>>>
>>>
>>> a - b
-1
>>>
>>> a.__sub__(b)
-1
>>> b.__rsub__(a)
-1
>>>
>>>
>>> b - a
1
>>>
>>> b.__sub__(a)
1
>>> a.__rsub__(b)
1

## 11.9.9. Use Case¶

>>> import numpy as np
>>>
>>>
>>> mylist = [1, 2, 3]
>>> myarr = np.array([4,5,6])
>>>
>>>
>>> myarr + mylist
array([5, 7, 9])
>>>
>>>
>>> mylist + myarr
array([5, 7, 9])
>>>
>>>
Traceback (most recent call last):
TypeError: can only concatenate list (not "numpy.ndarray") to list
>>>
array([5, 7, 9])
>>> class ndarray:
...         if isinstance(other, list):
...             other = np.array(other)
...         if isinstance(other, np.array):
...             ...
...
...         if isinstance(other, list):
...             other = np.array(other)
...         if isinstance(other, np.array):
...             ...

## 11.9.10. Use Case - 0x01¶

• Game

>>> hero @ Position(x=50, y=120)
>>>
>>> hero['gold'] += dragon['gold']

## 11.9.11. Use Case - 0x02¶

>>> from dataclasses import dataclass, field
>>>
>>>
>>> @dataclass
... class Astronaut:
...     firstname: str
...     lastname: str
>>>
>>>
>>> @dataclass
... class Crew:
...     members: list[Astronaut] = field(default_factory=list)
...
...         self.members.append(other)
...         return self
>>>
>>>
>>> ares3 = Crew()
>>> ares3 += Astronaut('Mark', 'Watney')
>>> ares3 += Astronaut('Melissa', 'Lewis')
>>>
>>> print(ares3)
Crew(members=[Astronaut(firstname='Mark', lastname='Watney'), Astronaut(firstname='Melissa', lastname='Lewis')])
>>>
>>> for member in ares3.members:
...     print(member)
Astronaut(firstname='Mark', lastname='Watney')
Astronaut(firstname='Melissa', lastname='Lewis')

## 11.9.12. Use Case - 0x03¶

a = np.arange(1, 10).reshape(3,3) a array([[1, 2, 3],

[4, 5, 6], [7, 8, 9]])

a = np.array([1,2,3]) b = [4,5,6] a array([1, 2, 3]) b [4, 5, 6] a + b array([5, 7, 9]) a.__add__(b) array([5, 7, 9]) b + a array([5, 7, 9]) b.__add__(a) TypeError: can only concatenate list (not "numpy.ndarray") to list a.__radd__(b) array([5, 7, 9])

## 11.9.13. Use Case - 0x04¶

This is our function library.

Transformation functions (non-reducing) - takes one argument and returns one value:

>>> def increment(x):
...     return x + 1
>>>
>>> def decrement(x):
...     return x - 1
>>>
>>> def square(x):
...     return x ** 2
>>>
>>> def cube(x):
...     return x ** 3

Reducing functions - takes two arguments returns one value:

...     return x + y
>>>
>>> def sub(x, y):
...     return x - y
>>>
>>> def mul(x, y):
...     return x * x

We have data to compute:

>>> data = [
...     [1, 2, 3],
...     [4, 5, 6],
...     [7, 8, 9],
... ]

On this data, we want to apply the following transformations:

>>> transformations = [increment, square, decrement, cube]

We need to create apply function, which takes data and apply the transformation:

>>> def apply(data, fn):
...     return map(fn, data)

Let's do it parallel. We will create three independent workers. Each worker will get part of the data (one-third) and will apply all the transformation (map) to their data subset.

>>> workerA = reduce(apply, transformations, data[0])  # [27, 512, 3375]
>>> workerB = reduce(apply, transformations, data[1])  # [13824, 42875, 110592]
>>> workerC = reduce(apply, transformations, data[2])  # [250047, 512000, 970299]

Note, that all workers will produce generators (maps). We need to merge the results using reduce function, but before that we need to evaluate maps to lists.

>>> def merge(x, y):
...     return list(x) + list(y)
>>> merged = reduce(merge, [workerA, workerB, workerC])
>>> print(result)
1903551
>>> print(merged)
[27, 512, 3375, 13824, 42875, 110592, 250047, 512000, 970299]

## 11.9.14. Assignments¶

Code 11.32. Solution
"""
* Assignment: Operator Numerical Matmul
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
1. Make object understand following call: position @ (1, 2)
1. Overload @ operator, to take tuple[int, int] as argument
2. Set x and y coordinates based on passed values
3. Run doctests - all must succeed

Polish:
1. Spraw aby obiekt obsługiwał to wywołanie: position @ (1, 2)
1. Przeciąż operator @, aby przyjmował tuple[int, int] jako argument
2. Zmień koordynaty x i y na podstawie przekazanych wartości
3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
* object.__matmul__()

Tests:
>>> import sys; sys.tracebacklimit = 0

>>> position = Position()
>>> position
Position(x=0, y=0)
>>> position @ (1, 2)
>>> position
Position(x=1, y=2)
"""

from dataclasses import dataclass

@dataclass
class Position:
x: int = 0
y: int = 0

Code 11.33. Solution
"""
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min

English:
1. Overload operator +=
2. Make Astronaut objects able to add Missions, for example:
a. mark = Astronaut(firstname='Mark', lastname='Watney')
b. mark += Mission(2035, 'Ares3')
c. mark += Mission(2040, 'Ares5')
3. Run doctests - all must succeed

Polish:
1. Przeciąż operator +=
2. Spraw aby do obiektów klasy Astronaut można dodać Mission, przykład:
a. mark = Astronaut(firstname='Mark', lastname='Watney')
b. mark += Mission(2035, 'Ares3')
c. mark += Mission(2040, 'Ares5')
3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
* object.__iadd__() -> self

Tests:
>>> import sys; sys.tracebacklimit = 0

>>> astro = Astronaut(firstname='Mark', lastname='Watney', missions=[
...     Mission(1969, 'Apollo 11'),
... ])
>>> astro += Mission(2024, 'Artemis 3')
>>> astro += Mission(2035, 'Ares 3')

>>> print(astro)  # doctest: +NORMALIZE_WHITESPACE
Astronaut(firstname='Mark', lastname='Watney',
missions=[Mission(year=1969, name='Apollo 11'),
Mission(year=2024, name='Artemis 3'),
Mission(year=2035, name='Ares 3')])
"""

from dataclasses import dataclass

@dataclass
class Astronaut:
firstname: str
lastname: str
missions: list

@dataclass
class Mission:
year: int
name: str