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
...
...     def __add__(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=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
...
...     def __iadd__(self, other):
...         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
...
...     def __add__(self, other):
...         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])
>>>
>>> a.__add__(b)
array([5, 7, 9])

Intuitive implementation:

>>> class ndarray:
...     def __add__(self, other):
...         ...

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])
>>>
>>> a.__add__(b)
array([5, 7, 9])

Intuitive implementation:

>>> class ndarray:
...     def __add__(self, other):
...         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])
>>>
>>> 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])
  • a + b will call a.__add__(b)

  • -> if value, then return result

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

Intuitive implementation:

>>> class ndarray:
...     def __add__(self, other):
...         if type(other) is list:
...             other = np.array(other)
...         ...
...
...     def __radd__(self, 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:
...     def __iadd__(self, other):
...         self.extend(other)
...
>>> class tuple:
...     def __iadd__(self, other):
...         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}'