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 indeks
Zapis danych przez indeks
A 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ątku
Jeśli indeks >= len, to będziemy dodawać element na końcu
W 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