# 8.1. Operator Arithmetic

• Left Operators

• Right Operators

• In-Place Operators

## 8.1.1. SetUp

>>> from dataclasses import dataclass


## 8.1.2. Numeric

• -obj -> obj.__neg__()

• +obj -> obj.__pos__()

• Example: -1 or +1

>>> @dataclass
... class Vector:
...     x: int
...     y: int
...
...     def __neg__(self): ...  # -obj
...     def __pos__(self): ...  # +obj


## 8.1.3. Left Operators

• obj + other -> obj.__add__(other)

• obj - other -> obj.__sub__(other)

• obj * other -> obj.__mul__(other)

• obj ** other -> obj.__pow__(other)

• obj @ other -> obj.__matmul__(other)

• obj / other -> obj.__truediv__(other)

• obj // other -> obj.__floordiv__(other)

• obj % other -> obj.__mod__(other)

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

>>> @dataclass
... class Vector:
...     x: int
...     y: int
...
...     def __sub__(self, other):
...         x = self.x - other.x
...         y = self.y - other.y
...         return Vector(x, y)
>>>
>>> a = Vector(1, 2)
>>> b = Vector(3, 4)
>>>
>>> a - b
Vector(x=-2, y=-2)


## 8.1.4. In-Place Operators

• obj += other -> obj.__iadd__(other)

• obj -= other -> obj.__isub__(other)

• obj *= other -> obj.__imul__(other)

• obj **= other -> obj.__ipow__(other)

• obj @= other -> obj.__imatmul__(other)

• obj /= other -> obj.__itruediv__(other)

• obj //= other -> obj.__ifloordiv__(other)

• obj %= other -> obj.__imod__(other)

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


In-Place operators will update object in-place, so the id (memory address) of the object will not change.

>>> a = Vector(1, 2)
>>> b = Vector(3, 4)
>>>
>>> id(a)
4789628672
>>>
>>> a += b
>>> a
Vector(x=4, y=6)
>>>
>>> id(a)
4789628672


In case of absence of __iadd__ method, Python will use __add__ method. Note, that __add__ method will return new object, so the id of the object will change.

>>> @dataclass
... class Vector:
...     x: int
...     y: int
...
...         x = self.x + other.x
...         y = self.y + other.y
...         return Vector(x, y)

>>> a = Vector(1,2)
>>> b = Vector(2,3)
>>>
>>> id(a)
4789628672
>>>
>>> a += b
>>> a
Vector(x=4, y=6)
>>>
>>> id(a)
4789628672


## 8.1.5. Right Operators

• obj + other - when obj.__add__(other) fail -> other.__radd__(obj)

• obj - other - when obj.__sub__(other) fail -> other.__rsub__(obj)

• obj * other - when obj.__mul__(other) fail -> other.__rmul__(obj)

• obj ** other - when obj.__pow__(other) fail -> other.__rpow__(obj)

• obj @ other - when obj.__matmul__(other) fail -> other.__rmatmul__(obj)

• obj / other - when obj.__truediv__(other) fail -> other.__rtruediv__(obj)

• obj // other - when obj.__floordiv__(other) fail -> other.__rfloordiv__(obj)

• obj % other - when obj.__mod__(other) fail -> other.__rmod__(obj)

## 8.1.6. Use Case - 0x01

>>> import numpy as np
>>>
>>> a = np.array([1, 2, 3])
>>> b = np.array([4, 5, 6])

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

>>> a + b
array([5, 7, 9])
>>>
array([5, 7, 9])


Intuitive implementation:

>>> class ndarray:
...         ...


## 8.1.7. Use Case - 0x02

>>> import numpy as np
>>>
>>> a = np.array([1, 2, 3])
>>> b = [4, 5, 6]

>>> type(a)
<class 'numpy.ndarray'>
>>>
>>> type(b)
<class 'list'>

>>> a + b
array([5, 7, 9])
>>>
array([5, 7, 9])


Intuitive implementation:

>>> class ndarray:
...         if type(other) is list:
...             other = np.array(other)
...         ...


## 8.1.8. Use Case - 0x03

>>> import numpy as np
>>>
>>> a = [1, 2, 3]
>>> b = np.array([4, 5, 6])

>>> a + b
array([5, 7, 9])
>>>
Traceback (most recent call last):
TypeError: can only concatenate list (not "numpy.ndarray") to list
>>>
array([5, 7, 9])

• a + b will call a.__add__(b)

• -> if value, then return result

• -> if exception, then call b.__radd__(a)

Intuitive implementation:

>>> class ndarray:
...         if type(other) is list:
...             other = np.array(other)
...         ...
...
...         if type(other) is list:
...             other = np.array(other)
...         ...


## 8.1.9. Use Case - 0x04

List:

>>> data = [1,2,3]
>>> data += [4,5,6]
>>>
>>> data
[1, 2, 3, 4, 5, 6]


Tuple:

>>> data = (1,2,3)
>>> data += (4,5,6)
>>>
>>> data
(1, 2, 3, 4, 5, 6)


Intuitive implementation:

>>> class list:
...         self.extend(other)
...
>>> class tuple:
...         return self + other


## 8.1.10. Use Case - 0x05

>>> 10 % 8
2
>>>
>>> '10' % 8
TypeError: not all arguments converted during string formatting
>>>
>>> '%s' % 8
'8'
>>>
>>> '10%s' % 8
'108'


Intuitive implementation:

>>> class int:
...     def __mod__(self, other):
...         # modulo division
...
>>> class str:
...     def __mod__(self, other):
...         # string formatting


Python 0.x, 1.x and 2.x (and still available in 3.x):

>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>> x = 'Hello %s' % firstname
>>> x = 'Hello %s %s' % (firstname, lastname)
>>> x = 'Hello %(fname)s %(lname)s' % {'fname': firstname, 'lname': lastname}


Python from 3.0 until 3.6:

>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>> x = 'Hello {} {}'.format(firstname, lastname)
>>> x = 'Hello {0} {1}'.format(firstname, lastname)
>>> x = 'Hello {1} {0}'.format(firstname, lastname)
>>> x = 'Hello {fname} {lname}'.format(fname=firstname, lname=lastname)


Python from 3.6 and above:

>>> firstname = 'Mark'
>>> lastname = 'Watney'
>>>
>>> x = f'Hello {firstname} {lastname}'