6.1 Metody magiczne
Przeciążanie operatorów w Pythonie pozwala określić lub zmienić zachowanie wbudowanych operatorów (na przykład +, -, *, /) dla klas użytkownika. Robi się to za pomocą specjalnych metod, które nazywają się metodami magicznymi.
Na przykład, w swojej klasie możesz przeciążyć operatory porównania:
| Operator | Metoda bez podkreślenia | Sygnatura metody |
|---|---|---|
| == | eq() |
__eq__(self, other) |
| != | ne() |
__ne__(self, other) |
| < | lt() |
__lt__(self, other) |
| <= | le() |
__le__(self, other) |
| > | gt() |
__gt__(self, other) |
| >= | ge() |
__ge__(self, other) |
Załóżmy, że napisałeś swoją klasę i chcesz, aby obiekty Twojej klasy były porównywane dokładnie tak, jak tego potrzebujesz. Musisz po prostu zaimplementować w swojej klasie metodę «__eq__», i Python będzie ją wywoływał za każdym razem, gdy w kodzie porównywane są obiekty Twojej klasy.
Przykład:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Użycie
v1 = Vector(2, 3)
v2 = Vector(2, 3)
v3 = Vector(4, 5)
print(v1 == v2) # Wyświetli: True
print(v1 == v3) # Wyświetli: False
Za każdym razem, kiedy porównujesz dwa swoje obiekty, Python sprawdza, czy mają zaimplementowaną funkcję «__eq__». Jeśli tak, to ją wywołuje. Jeśli nie ma, to porównuje tylko referencje do obiektów.
Faktycznie w przykładzie powyżej jest napisane (tylko trzeba jeszcze dodać sprawdzenie, czy metoda istnieje):
# Użycie
v1 = Vector(2, 3)
v2 = Vector(2, 3)
v3 = Vector(4, 5)
print(v1.__eq__(v2)) # Wyświetli: True
print(v1.__eq__(v3)) # Wyświetli: False
6.2 Lista wszystkich operatorów
W sumie do przeciążenia dostępnych jest 6 grup operatorów.
Operatory arytmetyczne:
| Operator | Metoda bez podkreślenia | Sygnatura metody |
|---|---|---|
| + | add |
__add__(self, other) |
| - | sub |
__sub__(self, other) |
| * | mul |
__mul__(self, other) |
| / | truediv |
__truediv__(self, other) |
| // | floordiv |
__floordiv__(self, other) |
| % | mod |
__mod__(self, other) |
| ** | pow |
__pow__(self, other) |
Operatory porównania:
| Operator | Metoda bez podkreślenia | Sygnatura metody |
|---|---|---|
| == | eq |
__eq__(self, other) |
| != | ne |
__ne__(self, other) |
| < | lt |
__lt__(self, other) |
| <= | le |
__le__(self, other) |
| > | gt |
__gt__(self, other) |
| >= | ge |
__ge__(self, other) |
Operatory logiczne:
| Operator | Metoda bez podkreślenia | Sygnatura metody |
|---|---|---|
| & | and |
__and__(self, other) |
| | | or |
__or__(self, other) |
| ^ | xor |
__xor__(self, other) |
| ~ | invert |
__invert__(self) |
Operatory indeksowania i wycinków:
| Operator | Metoda |
|---|---|
| obj[key] | __getitem__(self, key) |
| obj[key] = value | __setitem__(self, key, value) |
| del obj[key] | __delitem__(self, key) |
Operatory unarne:
| Operator | Metoda |
|---|---|
| - | __neg__(self) |
| + | __pos__(self) |
| abs() | __abs__(self) |
| ~ | __invert__(self) |
Operatory przypisania:
| Operator | Metoda |
|---|---|
| += | __iadd__(self, other) |
| -= | __isub__(self, other) |
| *= | __imul__(self, other) |
| /= | __itruediv__(self, other) |
| //= | __ifloordiv__(self, other) |
| %= | __imod__(self, other) |
| **= | __ipow__(self, other) |
Być może dlatego Python jest taki powolny – za każdym razem przed wykonaniem operatora szuka analogicznej funkcji w klasie i wszystkich jej klasach rodzicielskich. Ale to pozwala pisać najbardziej kompaktowy kod na świecie :)
6.3 Operator indeksacji
To, że można porównywać obiekty czy odejmować zbiory, jest w jakimś sensie oczywiste. Możesz sam się domyślić o tym, pisząc klasę, która zakłada na niej operacje logiczne lub matematyczne.
Chcę z Tobą przeanalizować taki ciekawy przykład – jak operator indeksacji. Zacznijmy od razu od przykładowego kodu:
class CustomList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __delitem__(self, index):
del self.data[index]
def __repr__(self):
return repr(self.data)
# Użycie
c_list = CustomList([1, 2, 3, 4, 5])
print(c_list[1]) # Wyświetli: 2
c_list[1] = 10
print(c_list) # Wyświetli: [1, 10, 3, 4, 5]
del c_list[1]
print(c_list) # Wyświetli: [1, 3, 4, 5]
Tu widzimy przykład trzech operacji:
Odczyt danych przez indeksZapis danych przez indeksA nawet usunięcie danych przez indeks.
A przecież wcale niekoniecznie, że dane wewnątrz będą przechowywane w formie listy. Lub indeks niekoniecznie musi być liczbą. Na przykład, klasa dictionary (słownik) jest właśnie tak zaimplementowana.
Tworzymy SuperList
Pamiętasz klasę list? Można jej przypisywać elementy, ale tylko te, które indeksy już są. Zróbmy swoją klasę, nazwiemy ją SuperList, do której elementów można będzie odnosić się po dowolnym indeksie:
Jeśli indeks < 0, to będziemy wstawiać element na początkuJeśli indeks >= len, to będziemy dodawać element na końcuW pozostałych przypadkach po prostu zwracamy element
Przykład:
class SuperList(list):
def __init__(self, value):
super().__init__(value)
def __setitem__(self, index, value):
if index >= len(self):
super().append(value)
elif index < 0:
super().insert(0, value)
else:
super().__setitem__(index, value)
lst = SuperList([1, 2, 3])
lst[200] = 100
lst[-200] = 99
print(lst) # [99, 1, 2, 3, 100]
Tak więc przeciążenie indeksów — świetna możliwość, i polecam korzystać z niej w praktyce. A na dzisiaj — to wszystko.
GO TO FULL VERSION