4.4. Operators¶
4.4.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'
>>> 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)
4.4.2. Numerical Operators¶
Operator |
Method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
%
(__mod__
) operator behavior for int
and str
:
>>> class Int:
... def __mod__(self, other):
... """modulo division"""
>>>
>>> 3 % 2
1
>>> 4 % 2
0
>>> class Str:
... def __mod__(self, other):
... """str substitute"""
...
... if type(other) is str:
... ...
... if type(other) is tuple:
... ...
... if type(other) is dict:
... ...
>>> 'Echo' % 2
Traceback (most recent call last):
TypeError: not all arguments converted during string formatting
>>> 'Echo %s' % 2
'Echo 2'
>>> 'Echo %d' % 2
'Echo 2'
>>> 'Echo %f' % 2
'Echo 2.000000'
>>> '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
4.4.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
4.4.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)
4.4.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
4.4.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
10
>>>
>>> data['x'] # data.__getitem__('x') -> data.__missing__() -> KeyError: 'x'
Traceback (most recent call last):
KeyError: x
>>>
>>> data() # data.__call__() -> TypeError: 'dict' object is not callable
Traceback (most recent call last):
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([[4, 5, 6]])
>>>
>>> data[1:2, 0]
array([4])
>>>
>>> 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)
array([4, 5, 6])
>>>
>>> data[1,2] # data.__getitem__((1,2))
6
>>>
>>> data[1:2] # data.__getitem__(1:2) # data.__getitem__(slice(1,2))
array([[4, 5, 6]])
>>>
>>> data[:, 2] # data.__getitem__((:, 2)) # data.__getitem__((slice(), 2))
array([3, 6])
4.4.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
4.4.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):
... if args not in self:
... self[args] = self.func(*args)
... return self[args]
>>>
>>>
>>> @Cache
... def add(a, b):
... return a + b
>>>
>>>
>>> _ = add(1,2) # computed
>>> _ = add(1,2) # fetched from cache
>>> _ = add(1,2) # fetched from cache
>>> _ = add(1,2) # fetched from cache
>>> _ = add(2,1) # computed
>>> _ = add(2,1) # fetched from cache
>>>
>>> add
{(1, 2): 3,
(2, 1): 3}
>>> 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 add(a, b):
... return a + b
>>>
>>>
>>> _ = add(1,2) # computed
>>> _ = add(1,2) # fetched from cache
>>> _ = add(1,2) # fetched from cache
>>> _ = add(1,2) # fetched from cache
>>> _ = add(2,1) # computed
>>> _ = add(2,1) # fetched from cache
>>>
>>> add
{(1, 2): 3,
(2, 1): 3}
4.4.9. Further Reading¶
4.4.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