8.4. Operator Right

  • x + y - if method "add" on object x fails, then call "radd" on object y (y.__radd__(x))

  • x - y - if method "sub" on object x fails, then call "rsub" on object y (y.__rsub__(x))

  • x * y - if method "mul" on object x fails, then call "rmul" on object y (y.__rmul__(x))

  • x ** y - if method "pow" on object x fails, then call "rpow" on object y (y.__rpow__(x))

  • x @ y - if method "matmul" on object x fails, then call "rmatmul" on object y (y.__rmatmul__(x))

  • x / y - if method "truediv" on object x fails, then call "rtruediv" on object y (y.__rtruediv__(x))

  • x // y - if method "floordiv" on object x fails, then call "rfloordiv" on object y (y.__rfloordiv__(x))

  • x % y - if method "mod" on object x fails, then call "rmod" on object y (y.__rmod__(x))

Table 8.3. Numerical Operator Overload

Operator

Method

obj + other

other.__radd__(obj)

obj - other

other.__rsub__(obj)

obj * other

other.__rmul__(obj)

obj ** other

other.__rpow__(obj)

obj @ other

other.__rmatmul__(obj)

obj / other

other.__rtruediv__(obj)

obj // other

other.__rfloordiv__(obj)

obj % other

other.__rmod__(obj)

8.4.1. Syntax

>>> class MyClass:
...     def __radd__(self, other): ...               # when a + b raise exception
...     def __rsub__(self, other): ...               # when a - b raise exception
...     def __rmul__(self, other): ...               # when a * b raise exception
...     def __rpow__(self, power, modulo=None): ...  # when a ** b raise exception
...     def __rmatmul__(self, other): ...            # when a @ b raise exception
...     def __rtruediv__(self, other): ...           # when a / b raise exception
...     def __rfloordiv__(self, other): ...          # when a // b raise exception
...     def __rmod__(self, other): ...               # when a % b raise exception

8.4.2. Recap

>>> class Vector:
...     x: int
...     y: int
...
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __add__(self, other):
...         return Vector(x=self.x+other.x, y=self.y+other.y)
>>>
>>>
>>> a = Vector(x=1, y=2)
>>> b = Vector(x=3, y=4)
>>>
>>> a + b
Vector(x=4, y=6)

8.4.3. Left Operation

Problem:

>>> class Vector:
...     x: int
...     y: int
...
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __add__(self, other):
...         return Vector(x=self.x+other.x, y=self.y+other.y)
>>>
>>>
>>> a = Vector(x=1, y=2)
>>> b = (3, 4)
>>>
>>> a + b
Traceback (most recent call last):
AttributeError: 'tuple' object has no attribute 'x'

Solution:

>>> class Vector:
...     x: int
...     y: int
...
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __add__(self, other):
...         if isinstance(other, tuple):
...             other = Vector(*other)
...         return Vector(x=self.x+other.x, y=self.y+other.y)
>>>
>>>
>>> a = Vector(x=1, y=2)
>>> b = (3, 4)
>>>
>>> a + b
Vector(x=4, y=6)
>>>
>>> a.__add__(b)
Vector(x=4, y=6)

8.4.4. Right Operation

Problem:

>>> class Vector:
...     x: int
...     y: int
...
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __add__(self, other):
...         if isinstance(other, tuple):
...             other = Vector(*other)
...         return Vector(x=self.x+other.x, y=self.y+other.y)
>>>
>>>
>>> a = (1, 2)
>>> b = Vector(x=3, y=4)
>>>
>>> a + b
Traceback (most recent call last):
TypeError: can only concatenate tuple (not "Vector") to tuple
>>>
>>> a.__add__(b)
Traceback (most recent call last):
TypeError: can only concatenate tuple (not "Vector") to tuple

Solution:

>>> class Vector:
...     x: int
...     y: int
...
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __add__(self, other):
...         if isinstance(other, tuple):
...             other = Vector(*other)
...         return Vector(x=self.x+other.x, y=self.y+other.y)
...
...     def __radd__(self, other):
...         return self.__add__(other)
>>>
>>>
>>> a = (1, 2)
>>> b = Vector(x=3, y=4)
>>>
>>> a + b
Vector(x=4, y=6)
>>>
>>> a.__add__(b)
Traceback (most recent call last):
TypeError: can only concatenate tuple (not "Vector") to tuple
>>>
>>> b.__radd__(a)
Vector(x=4, y=6)

8.4.5. Case Study

a = list([1, 2])
b = list([3, 4])

# %%

a + b
# [1, 2, 3, 4]

a.__add__(b)
# [1, 2, 3, 4]

# %%

type(a)
# <class 'list'>

type(b)
# <class 'list'>

# %%

class list:
    def __add__(self, other):
        ...
import numpy as np


a = np.array([1, 2])
b = np.array([3, 4])

# %%

a + b
# array([4, 6])

a.__add__(b)
# array([4, 6])

# %%

type(a)
# <class 'numpy.ndarray'>

type(b)
# <class 'numpy.ndarray'>

# %%

class ndarray:
    def __add__(self, other):
        ...
import numpy as np


a = np.array([1, 2])
b = list([3, 4])

# %%

a + b
# array([4, 6])

a.__add__(b)
# array([4, 6])

# %%

type(a)
# <class 'numpy.ndarray'>

type(b)
# <class 'list'>

# %%

class ndarray:
    def __add__(self, other):
        if isinstance(other, list):
            other = np.array(other)
        ...
import numpy as np


a = list([1, 2])
b = np.array([3, 4])

# %%

a + b
# array([4, 6])

a.__add__(b)
# TypeError: can only concatenate list (not "numpy.ndarray") to list

# %%

b.__radd__(a)
# array([4, 6])

# %%

type(a)
# <class 'list'>

type(b)
# <class 'numpy.ndarray'>

# %%

class ndarray:
    def __radd__(self, other):
        return self + other

    def __add__(self, other):
        if isinstance(other, list):
            other = np.array(other)
        ...

8.4.6. 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])
>>>
>>>
>>> mylist.__add__(myarr)
Traceback (most recent call last):
TypeError: can only concatenate list (not "numpy.ndarray") to list
>>>
>>> myarr.__radd__(mylist)
array([5, 7, 9])
>>> class ndarray:
...     def __add__(self, other):
...         if isinstance(other, list):
...             other = np.array(other)
...         if isinstance(other, np.array):
...             ...
...
...     def __radd__(self, other):
...         if isinstance(other, list):
...             other = np.array(other)
...         if isinstance(other, np.array):
...             ...

8.4.7. Use Case - 1

  • Game

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

8.4.8. Use Case - 2

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

8.4.9. Use Case - 3

SetUp:

>>> import numpy as np

Example 1:

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>>
>>> a + b
[1, 2, 3, 4, 5, 6]
>>>
>>> a.__add__(b)
[1, 2, 3, 4, 5, 6]

Example 2:

>>> a = np.array([1, 2, 3])
>>> b = np.array([4, 5, 6])
>>>
>>> a + b
array([5, 7, 9])
>>>
>>> a.__add__(b)
array([5, 7, 9])

Example 3:

>>> a = np.array([1, 2, 3])
>>> b = [4, 5, 6]
>>>
>>> a + b
array([5, 7, 9])
>>>
>>> a.__add__(b)
array([5, 7, 9])

Why this works:

>>> class ndarray:
...     def __add__(self, other):
...         if type(other) is not ndarray:
...             other = np.array(other)

Example 4:

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

Why this works:

>>> class ndarray:
...     def __add__(self, other):
...         if type(other) is not ndarray:
...             other = np.array(other)
...
...     def __radd__(self, other):
...         if type(other) is not ndarray:
...             other = np.array(other)

8.4.10. Assignments