Metody specjalne
Metody z podwójnymi podkreślnikami, dzięki którym obiekty działają z operatorami ==, <, +, in oraz z funkcją len().
Przykład
#class Score:
def __init__(self, student, points):
self.student = student
self.points = points
def __eq__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.points == other.points
def __lt__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.points < other.points
def __repr__(self):
return f"Score({self.student!r}, {self.points})"
a = Score("Ania", 92)
b = Score("Kuba", 78)
c = Score("Ola", 92)
print(a == c, a != b)
print(b < a, a > b)
print(sorted([a, b, c]))
print(max([a, b, c]))Definicja i zastosowanie
#Metody specjalne, nazywane też magicznymi albo „dunder” (od double underscore), to metody o nazwach otoczonych podwójnymi podkreślnikami. Zwykle nie wywołuje się ich bezpośrednio: robi to Python, gdy użyjesz operatora albo funkcji wbudowanej. Zapis a == b zamienia się w a.__eq__(b), a + b w a.__add__(b), a len(a) w a.__len__().
Dzięki nim własne obiekty zachowują się jak typy wbudowane: wyniki można porównywać i sortować, punkty dodawać operatorem +, a kurs traktować jak kolekcję lekcji z len() i in. Takie przeciążanie operatorów ma sens, gdy znaczenie operacji jest oczywiste. Dodawanie punktów jest czytelne, dodawanie dwóch uczniów raczej nie.
Gdy metoda nie obsługuje typu drugiego argumentu, powinna zwrócić specjalną wartość NotImplemented, a nie zgłaszać wyjątek. Python spróbuje wtedy operacji odwrotnej po stronie drugiego obiektu (np. __radd__), a jeśli i ta zawiedzie, zgłosi czytelny TypeError.
Uwaga na równość i hashowanie: klasa, która definiuje __eq__, ale nie __hash__, nie może już być kluczem słownika ani elementem zbioru. Brakujące porównania <=, > i >= na podstawie __eq__ i __lt__ uzupełni dekorator functools.total_ordering.
Składnia
#class Nazwa:
def __eq__(self, other): ...
def __lt__(self, other): ...
def __add__(self, other): ...
def __len__(self): ...
def __contains__(self, item): ...Najczęstsze metody specjalne
#__eq__()
a == b
Równość. Bez tej metody obiekt jest równy tylko samemu sobie. Operator!=automatycznie zwraca odwrotny wynik.__lt__()
a < b
Porównanie „mniejszy niż”. Wystarcza do sortowania przezsorted()oraz domin()imax(), aa > bPython obsłuży jakob < a.__add__()
a + b
Dodawanie. Powinno zwracać nowy obiekt, a dla nieobsługiwanego typu wartośćNotImplemented.__radd__()
b + a
Dodawanie, gdy obiekt stoi po prawej stronie, a lewy argument nie umie go dodać. Dzięki niemu działasum(), które zaczyna od 0.__len__()
len(a)
Długość. Musi zwracać nieujemną liczbę całkowitą. Obiekt o długości 0 jest fałszywy w warunkach.__contains__()
x in a
Sprawdzenie, czy element należy do obiektu. Bez tej metodyinprzeszuka elementy, jeśli po obiekcie da się iterować.__hash__()
hash(a)
Hash, potrzebny w zbiorach i kluczach słownika. Musi opierać się na tych samych polach co__eq__.
Więcej przykładów
#class Points:
def __init__(self, value):
self.value = value
def __add__(self, other):
if isinstance(other, Points):
return Points(self.value + other.value)
if isinstance(other, int):
return Points(self.value + other)
return NotImplemented
def __radd__(self, other):
return self + other
def __repr__(self):
return f"Points({self.value})"
print(Points(20) + Points(15))
print(5 + Points(20))
print(sum([Points(10), Points(25), Points(5)]))
try:
Points(10) + "dużo"
except TypeError as error:
print(error)class Course:
def __init__(self, title, lessons):
self.title = title
self.lessons = list(lessons)
def __len__(self):
return len(self.lessons)
def __contains__(self, topic):
return topic.lower() in (lesson.lower() for lesson in self.lessons)
python = Course("Python", ["Zmienne", "Pętle", "Funkcje"])
soon = Course("Wkrótce", [])
print(len(python))
print("pętle" in python, "klasy" in python)
print(bool(python), bool(soon))class Badge:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return isinstance(other, Badge) and self.name == other.name
def __hash__(self):
return hash(self.name)
earned = {Badge("Start"), Badge("Seria 7"), Badge("Start")}
print(len(earned))
print(Badge("Seria 7") in earned)Bez metody __hash__ utworzenie zbioru zgłosiłoby TypeError, bo klasa z własnym __eq__ traci domyślny hash.
Dobre praktyki
#- Metody porównań i działań zwracają
NotImplementeddla nieobsługiwanych typów, zamiast zgłaszać wyjątek. Python spróbuje wtedy operacji po stronie drugiego obiektu. - Operatory arytmetyczne powinny tworzyć nowy obiekt. Zmiana
selfwewnątrz__add__zaskoczy każdego, kto użyje+. - Komplet porównań dostaniesz dekoratorem
functools.total_orderingalbo przez@dataclass(order=True).
Powiązane hasła
#- __str__() i __repr__()Metody określające tekstowy wygląd obiektu: czytelny dla użytkownika (__str__) i techniczny dla programisty (__repr__).
- @dataclassDekorator, który na podstawie pól z adnotacjami typów generuje __init__, __repr__ i __eq__ klasy przechowującej dane.
- len()Zwraca liczbę elementów w kolekcji albo liczbę znaków w napisie.
- sorted()Zwraca nową, posortowaną listę z elementów dowolnej kolekcji.
- functoolsNarzędzia do pracy z funkcjami: zapamiętywanie wyników (lru_cache), częściowe wywołania (partial) i zwijanie kolekcji (reduce).
- classDefiniuje klasę: szablon obiektów łączący dane i funkcje, które na nich działają.
Widzisz błąd albo brakuje przykładu? Napisz do nas.