6.6. Operators¶
6.6.1. Rationale¶
Operator Overload
Readable syntax
Simpler operations
Following examples uses
dataclasses
to focus on action code, not boilerplate
from dataclasses import dataclass
@dataclass
class Vector:
x: int = 0
y: int = 0
Vector(x=1, y=2) + Vector(x=3, y=4)
# Traceback (most recent call last):
# TypeError: unsupported operand type(s) for +: 'Vector' and 'Vector'
Vector(x=1, y=2) + Vector(x=3, y=4) + Vector(x=5, y=6)
# Traceback (most recent call last):
# TypeError: unsupported operand type(s) for +: 'Vector' and 'Vector'
from dataclasses import dataclass
@dataclass
class Vector:
x: int = 0
y: int = 0
def __add__(self, other):
return Vector(
self.x + other.x,
self.y + other.y)
Vector(x=1, y=2) + Vector(x=3, y=4)
# Vector(x=4, y=6)
Vector(x=1, y=2) + Vector(x=3, y=4) + Vector(x=5, y=6)
# Vector(x=9, y=12)
6.6.2. Numerical Operators¶
Operator |
Method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
%
(__mod__
) operator behavior for int
and str
:
class int:
def __mod__(self, other):
"""modulo division"""
class str:
def __mod__(self, other):
"""str substitute"""
if type(other) is str:
...
if type(other) is tuple:
...
if type(other) is dict:
...
3 % 2 # 1
4 % 2 # 0
'Echo' % 2 # TypeError: not all arguments converted during string formatting
'Echo %s' % 2 # 'Echo 2'
'Echo %d' % 2 # 'Echo 2'
'Echo %f' % 2 # 'Echo 2.0'
'Echo %s %s' % (1, 2) # 'Echo 1 2'
'Echo %s %d %f' % (1, 2, 3) # 'Echo 1 2 3.000000'
'Echo %(firstname)s %(lastname)s' % {'firstname': 'Mark', 'lastname': 'Watney'}
# 'Echo Mark Watney'
'Echo %(name)s %(age)d' % {'name': 'Mark Watney', 'age': 44}
# 'Echo Mark Watney 44'
%s
, %d
, %f
is currently deprecated in favor of f'...'
string formatting.
More information in Builtin Printing.
6.6.3. Comparison Operators¶
Operator |
Method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
@dataclass
class Vector:
x: int = 0
y: int = 0
def __eq__(self, other):
if (self.x == other.x) and (self.y == other.y):
return True
else:
return False
Vector(x=1, y=2) == Vector(x=3, y=4)
# False
Vector(x=1, y=2) == Vector(x=1, y=2)
# True
6.6.4. Boolean Operators¶
Operator |
Method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
from dataclasses import dataclass
@dataclass
class Digit:
value: int
def __xor__(self, other):
return Digit(self.value ** other.value)
a = Digit(2)
b = Digit(4)
a ^ b
# Digit(value=16)
6.6.5. Builtin Functions and Keywords¶
Function |
Method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from math import sqrt
from dataclasses import dataclass
@dataclass
class Vector:
x: int = 0
y: int = 0
def __abs__(self):
return sqrt(self.x**2 + self.y**2)
abs(Vector(x=3, y=4))
# 5.0
class Astronaut:
def __float__(self) -> float:
return 1961.0
def __int__(self) -> int:
return 1969
def __len__(self) -> int:
return 170
def __str__(self) -> str:
return 'My name... José Jiménez'
def __repr__(self) -> str:
return f'Astronaut()'
astro = Astronaut()
float(astro)
# 1961.0
int(astro)
# 1969
len(astro)
# 170
repr(astro)
# Astronaut()
str(astro)
# 'My name... José Jiménez'
print(astro)
# My name... José Jiménez
6.6.6. Accessors Overload¶
Operator |
Method |
Remarks |
---|---|---|
|
|
|
|
|
|
|
|
(when |
|
|
|
|
|
|
|
|
>>> data = slice(1, 2, 3)
>>>
>>> data.start
1
>>> data.stop
2
>>> data.step
3
class MyClass:
def __getitem__(self, item):
print(item)
my = MyClass()
my[1:2]
# slice(1, 2, None)
data = dict()
data['a'] = 10
# data.__setitem__('a', 10) -> None
data['a']
# data.__getitem__('a') -> 10
data['x']
# data.__getitem__('x') -> data.__missing__() -> KeyError: 'x'
data()
# data.__call__() -> TypeError: 'dict' object is not callable
Contains in numpy
:
import numpy as np
data = np.array([[1, 2, 3],
[4, 5, 6]])
data[1][2]
# 6
data[1,2]
# 6
data[1:2]
# array([[2,3],
# [5,6]])
data[1:2, 0]
# array([2,3])
data[1:2, 1:]
# array([[5,6]])
Intuitive implementation of numpy array[row,col]
accessor:
class array(list):
def __getitem__(key):
if isinstance(key, int):
return super().__getitem__(key)
if isinstance(key, tuple):
row = key[0]
col = key[1]
return super().__getitem__(row).__getitem__(col)
if isinstance(key, slice):
start = key[0] if key[0] else 0
stop = key[1] if key[0] else len(self)
step = key[2] if key[2] else 1
return ...
data[1]
# data.__getitem__(1)
data[1,2]
# data.__getitem__((1,2))
data[1:2]
# data.__getitem__(1:2)
# data.__getitem__(slice(1,2))
data[:, 2]
# data.__getitem__((:, 2))
# data.__getitem__((slice(), 2))
6.6.7. Eq Works at Both Sides¶
class Astronaut:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
class Cosmonaut:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
a = Astronaut('Mark', 'Watney')
c = Cosmonaut('Mark', 'Watney')
print(a == c)
# False
class Astronaut:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def __eq__(self, other):
return (self.firstname == other.firstname)
and (self.lastname == other.lastname)
class Cosmonaut:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
a = Astronaut('Mark', 'Watney')
c = Cosmonaut('Mark', 'Watney')
print(a == c)
# True
class Astronaut:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
class Cosmonaut:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def __eq__(self, other):
return (self.firstname == other.firstname)
and (self.lastname == other.lastname)
a = Astronaut('Mark', 'Watney')
c = Cosmonaut('Mark', 'Watney')
print(a == c)
# True
6.6.8. Use Cases¶
hero @ Position(x=50, y=120)
hero >> Direction(left=10, up=20)
hero < Damage(20)
hero > Damage(20)
hero["gold"] += dragon["gold"]
class Cache(dict):
def __init__(self, func):
self._func = func
def __call__(self, *args):
return self[args]
def __missing__(self, key):
self[key] = self._func(*key)
return self[key]
@Cache
def myfunction(a, b):
return a * b
myfunction(2, 4) # 8 # Computed
myfunction('hi', 3) # 'hihihi' # Computed
myfunction('ha', 3) # 'hahaha' # Computed
myfunction('ha', 3) # 'hahaha' # Fetched from cache
myfunction('hi', 3) # 'hihihi' # Fetched from cache
myfunction(2, 4) # 8 # Fetched from cache
myfunction(4, 2) # 8 # Computed
myfunction
# {
# (2, 4): 8,
# ('hi ', 3): 'hihihi',
# ('ha', 3): 'hahaha',
# (4, 2): 8,
# }
6.6.9. Further Reading¶
6.6.10. Assignments¶
"""
* Assignment: OOP Overload Matmul
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min
English:
1. Use code from "Given" section (see below)
2. Overload `@` operator
3. Set position based on argument `tuple[int, int]`
4. Compare result with "Tests" section (see below)
Polish:
1. Użyj kodu z sekcji "Given" (patrz poniżej)
2. Przeciąż operator `@`
3. Ustaw pozycję na podstawie argumentu `tuple[int, int]`
4. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> position = Position()
>>> position
Position(x=0, y=0)
>>> position @ (1, 2)
>>> position
Position(x=1, y=2)
"""
# Given
from dataclasses import dataclass
@dataclass
class Position:
x: int = 0
y: int = 0
"""
* Assignment: OOP Overload IAdd
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min
English:
1. Use code from "Given" section (see below)
2. Override operator `+=` for code to work correctly
3. Compare result with "Tests" section (see below)
Polish:
1. Użyj kodu z sekcji "Given" (patrz poniżej)
2. Nadpisz operatory `+=` aby poniższy kod zadziałał poprawnie
3. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Hints:
* `obj.__iadd__(other) -> self`
Tests:
>>> astro = Astronaut('Jan Twardowski', missions=[
... Mission(1969, 'Apollo 11'),
... ])
>>> astro += Mission(2024, 'Artemis 3')
>>> astro += Mission(2035, 'Ares 3')
>>> print(astro) # doctest: +NORMALIZE_WHITESPACE
Astronaut(name='Jan Twardowski',
missions=[Mission(year=1969, name='Apollo 11'),
Mission(year=2024, name='Artemis 3'),
Mission(year=2035, name='Ares 3')])
"""
# Given
from dataclasses import dataclass
@dataclass
class Astronaut:
name: str
missions: list
@dataclass
class Mission:
year: int
name: str
"""
* Assignment: OOP Overload Equals
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min
English:
1. Use code from "Given" section (see below)
2. Override operator for code to work correctly
3. Do not use `dataclasses`
4. Compare result with "Tests" section (see below)
Polish:
1. Użyj kodu z sekcji "Given" (patrz poniżej)
2. Nadpisz operator aby poniższy kod zadziałał poprawnie
3. Nie używaj `dataclasses`
4. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> Mission(2035, 'Ares 3') == Mission(2035, 'Ares 3')
True
>>> Mission(2035, 'Ares 3') == Mission(1973, 'Apollo 18')
False
>>> Mission(2035, 'Ares 3') == Mission(2035, 'Apollo 18')
False
>>> Mission(2035, 'Ares 3') == Mission(1973, 'Ares 3')
False
"""
# Given
class Mission:
def __init__(self, year, name):
self.year = year
self.name = name
"""
* Assignment: OOP Overload Contains
* Complexity: easy
* Lines of code: 5 lines
* Time: 8 min
English:
1. Use code from "Given" section (see below)
2. Override operators for code to work correctly
3. Do not use `dataclasses`
4. Compare result with "Tests" section (see below)
Polish:
1. Użyj kodu z sekcji "Given" (patrz poniżej)
2. Nadpisz operatory aby poniższy kod zadziałał poprawnie
3. Nie używaj `dataclasses`
4. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> astro = Astronaut('Jan Twardowski', missions=[
... Mission(1969, 'Apollo 11'),
... Mission(2024, 'Artemis 3'),
... Mission(2035, 'Ares 3'),
... ])
>>> Mission(2035, 'Ares 3') == Mission(2035, 'Ares 3')
True
>>> Mission(2035, 'Ares 3') == Mission(1973, 'Apollo 18')
False
>>> Mission(2035, 'Ares 3') == Mission(2035, 'Apollo 18')
False
>>> Mission(2035, 'Ares 3') == Mission(1973, 'Ares 3')
False
>>> Mission(2024, 'Artemis 3') in astro
True
>>> Mission(1973, 'Apollo 18') in astro
False
"""
# Given
class Astronaut:
def __init__(self, name, missions):
self.name = name
self.missions = missions
class Mission:
def __init__(self, year, name):
self.year = year
self.name = name