programowanieProgramista Backend

Czym są metody magiczne (metody dunder) w Pythonie, jak są używane i po co je nadpisywać w swoich klasach? Podaj przykłady ich zastosowania i omów niuanse, które warto uwzględnić przy ich implementacji.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Metody magiczne (lub metody dunder – od podwójnego podkreślenia) to specjalne metody, których nazwy zaczynają się i kończą dwoma podkreśleniami, na przykład __init__, __str__, __add__. Umożliwiają one wbudowanie instancji własnych klas w składnię i zachowanie Pythona: określanie reakcji na operacje arytmetyczne, porównania, konwersję na ciągi znaków, pracę z protokołami kolekcji itp.

Przykład:

class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __str__(self): return f"Vector({self.x}, {self.y})" v1 = Vector(1, 2) v2 = Vector(3, 4) print(v1 + v2) # Wywoła v1.__add__(v2), zwróci Vector(4, 6)

Pytanie z podchwytliwością.

Jeśli nie zaimplementuje się __eq__ w swojej klasie, jak będą porównywane jej instancje za pomocą operatora ==?

Odpowiedź: Domyślnie operator == porównuje obiekty według tożsamości (adresu w pamięci, podobnie jak operator is), jeśli nie nadpisano metody magicznej __eq__.

class A: pass print(A() == A()) # False, chociaż obiekty "wydają się identyczne"

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu.


Historia

W projekcie, w którym trzeba było porównywać struktury grafu, nie zaimplementowano metody __eq__. W wyniku tego sprawdzanie "czy węzeł już został dodany" dawało niepoprawny wynik, ponieważ operator == porównywał obiekty według id, a nie według zawartości.


Historia

Podczas pisania REST API zserializowano obiekt do ciągu znaków w logach przez str(obj), ale zapomniano zdefiniować __str__. W rezultacie wyświetlano nieczytelny tekst <MyObj object at 0x...>, co utrudniało diagnostykę problemów.


Historia

W bibliotece do obliczeń matematycznych zaimplementowano tylko __add__, ale zapomniano o __iadd__ dla operatora +=. W rezultacie wyrażenie v += w działało nie zgodnie z oczekiwaniami (tworzony był nowy obiekt, nie aktualizowano starego), co prowadziło do wycieków pamięci w złożonych obliczeniach.