https://docs.python.org/3/reference/datamodel.html
W Pythonie nie ma przeciążania funkcji, bo nazwa funkcji jest referencją do obiektu. W C++ mogą być funkcje o tej samej nazwie, które różnią się typami argumentów (ale nie zwracanych wartości). W Pythonie przeciążanie operatorów robi się za pomocą metod specjalnych.
W uproszczeniu przeciążanie operatorów pozwala obiektom zapisanym za pomocą klas przechwytywać i odpowiadać na operacje, które działają na typach wbudowanych. Są to operacje dodawania, wyświetlania, tworzenia wycinków, itp. W ten sposób obiekty własne użytkownika zachowują się jak obiekty wbudowane, co powoduje powstanie bardziej spójnych i łatwiejszych do zrozumienia interfejsów.
Metody zawierające w nazwie podwójne znaki podkreślenia (jak __X__) są specjalnymi punktami zaczepienia. Takie metody wywoływane są automatycznie, kiedy instancje pojawią się w operacjach wbudowanych. Klasy mogą nadpisywać większość operacji wbudowanych. Nie istnieją wartości domyślne dla metod przeciążania operatorów (i nie są one potrzebne).
# Wybrane metody specjalne. __new__ pierwsza metoda wywoływana przy inicjalizacji obiektu, rzadko definiowana jawnie, przekazuje argumenty do __init__; [__new__(cls, arg1, arg2, ...): ...] __init__ konstruktor __del__ destruktor (finalizer), działa gdy licznik referencji zejdzie do zera i zaczyna pracować garbage collection; wyrażenie 'del x' tylko zmniejsza licznik referencji o 1; wyrażenie 'del x' _nie_ przekłada się na x.__del__(); __str__ zamiana na "nieformalny" string, wywoływane przez str(x); metoda działa, jeżeli obiekt jest dostępny bezpośrednio; __repr__ "oficjalna" reprezentacja obiektu przez string repr(x); powinna wyglądać jak poprawne wyrażenie w Pythonie do stworzenia obiektu na nowo; zwraca wartość w sesji interaktywnej; metoda działa również wtedy, gdy obiekt jest zagnieżdżony w innym obiekcie, np. jest elementem listy; __unicode__ wywoływane przez unicode(x) (Py2); __format__(self, formatstr) np. "Obiekt {}".format(x); __hash__ wywoływane przez hash(x), zwraca int; It is advised to mix together the hash values of the components of the object that also play a part in comparison of objects by packing them into a tuple and hashing the tuple. Jeżeli klasa nie definiuje __eq__, to nie powinna definiować __hash__. Dla obiektów mutowalnych nie należy definiować __hash__. __bytes__ wywoływane przez bytes(x), zwraca obiekt bytes; __round__ wywoływane przez round(x, ndigits=None), zaokrąglanie; metoda stosowana do typów skalarnych, liczb (np. wiek człowieka).
# Operatory dwuargumentowe. __add__ dodawanie, x + y __sub__ odejmowanie, x - y __mul__ mnożenie, x * y __mod__ reszta z dzielenia, x % y __divmod__ zwraca krotkę (div, mod), divmod(x, y) __pow__ potęgowanie, pow(x, y) lub x ** y; powinien być opcjonalny trzeci argument, jeżeli ma być wspierane pow(x, y, z)]
# Definiowanie operatora dzielenia /. __div__ dzielenie klasyczne, x / y (Py2) __floordiv__ dzielenie całkowite, x // y (Py2 i Py3) __truediv__ dzielenie prawdziwe, x / y (Py3), działa w Py2, jeżeli jest aktywne __future__.division
# Reflected arithmetic operators. # Wersje prawe operatorów, które wywoływane są wtedy, gdy obiekt # po prawej stronie operatora jest naszą instancją klasy, jednak # obiekt z lewej strony nie jest instancją naszej klasy. # Wersje prawe operatorów są potrzebne dla operacji przemiennych. # W wersjach prawych operatorów PRAWY argument staje się self. # Dla działania nieinstancja + instancja mamy # def __radd__(self, other): ... # w kodzie self będzie instancją, a other nieinstancją. # Nieinstancja nie może definiować __add__ lub powinna zwracać obiekt # NotImplemented dla naszego typu (to nie jest wyjątek NotImplementedError). __radd__ right add __rsub__ right sub __rmul__ right mul __rdiv__ right div __rtruediv__ right truediv __rfloordiv__ right floordiv __rmod__ reszta z dzielenia __rdivmod__ zwraca tuple __rpow__ potęgowanie
# Augmented arithmetic assignments powinny być robione in-place # (zmieniać self) i zwracać wynik (niekoniecznie self). __iadd__ add (x += y) __isub__ sub (x -= y) __imul__ mul (x *= y) __idiv__ (x /= y) __itruediv__ div (x //= y) __ifloordiv__ (x %= y) __imod__
# Przykład. alist = [2] alist += [5, 6] # zmiana obiektu! odpowiada alist.extend([5, 6]) print(alist) # [2, 5, 6] # To jest równoważne działaniom: alist = alist.__iadd__([5, 6]) # potrzebne return self # Jeżeli klasa nie ma metody __iadd__, operacja A += B zamieniana jest # na A = A + B, co działa też dla obiektów niezmiennych (str, tuple). # Przykład klasy dla obiektów zmiennych. class Time: def __iadd__(self, other): # działanie time1 += time2 self.s += other.s return self # typowe zachowanie
# Operatory bitowe. __lshift__(self, other) left bitwise shift, operator << __rshift__(self, other) right bitwise shift, operator >> __and__(self, other) bitowe and, operator & __or__(self, other) bitowe or, operator | __xor__(self, other) bitowe xor, operator ^ Są jeszcze prawe (i augmented) wersje operatorów bitowych.
# Operatory jednoargumentowe. __pos__ znak plus, +x __neg__ znak minus, -x __abs__ wartość bezwzględna, abs(x) __invert__ zwykle odwrotność lub negacja, ~x __complex__ implementacja complex(x) __int__ implementacja int(x) __long__ implementacja long(x) (Py2) __float__ implementacja float(x) __complex__ implementacja complex(x)
# Porównywanie. __cmp__ porównywanie (Py2), zwraca -1|0|+1 __len__ długość kontenera, len(x) __nonzero__ test prawdziwości (Py2), jeżeli obiekt nie jest kontenerem __bool__ test prawdziwości (Py3), bool(x) __bool__ = __nonzero__ # kod dla zachowania kompatybilności Pythona 2 i 3 # Tak zwane "rich comparison" methods (Py2.6+, Py3). # Jeżeli nie są zdefiniowane, wywoływane jest __cmp__ __lt__ mniejsze, x < y __gt__ większe, x > y __eq__ równe, x == y [bez tego działa id(x) == id(y)] __le__ mniejsze lub równe, x <= y __ge__ większe lub równe, x >= y __ne__ różne, x != y, dawniej też x <> assert (x < y) == (y > x) # jedno można obliczyć z drugiego assert (x <= y) == (y >= x) # jedno można obliczyć z drugiego assert (x == y) == not (x != y) # jedno można obliczyć z drugiego # Jeżeli brak __ne__, to dla klas użytkownika działa not (x == y). # Dla klas dziedziczonych z klas wbudowanych nie musi tak być. # PEP 207 # min() and list.sort() operations only use the < operator (less). # max() only use the > operator. # 'in' and 'not in' operators and dictionary lookup only use the == operator. # The reflexivity rules are assumed by Python. # x > y jest równe y < x (interpreter może przestawić).
# Emulowanie typów kontenerowych. __len__ # len(x) Długość kontenera, testy prawdziwości, jeśli nie ma __bool__. Metoda wymaga, aby zwracana wartość była typu int. __iter__ # iter(x) This method is called when an iterator is required for a container. __reversed__ # reversed(x) It should return a new iterator that iterates over all the objects in the container in reverse order. Jeżeli __reversed__ nie zdefiniowano, wtedy reversed() korzysta z __len__ i __getitem__.
# Zarządzanie atrybutami __setattr__(self, "attr", value) # X.attr = value Przypisanie atrybutu do instancji. __delattr__(self, "attr") # del X.attr Usuwanie atrybutu. __getattr__(self, "attr") # X.attr Funkcja wywoływana przy próbie korzystania z atrybutu, który nie istnieje. Można go wtedy uzupełnić, np. self.__dict__["attr"] = some_value Zwyczajnie przy próbie X.attr Python wykona return self.__dict__["attr"] # kluczem jest string __getattribute__(self, "attr") # X.attr Przechwytywanie atrybutu, który może istnieć lub nie. Może być trudne do zaimplementowania bez popadnięcia w nieskończoną pętlę. Należy się odwołać do klasy bazowej, np. super(Foo, self).__getattribute__("attr") __getitem__ indeksowanie, wycinanie (Py3), iteracje, X[key], X[i:j], pętle for oraz inne iteracje, jeśli nie ma __iter__ def __getitem__(self, key): pass # kod dla X[key] __setitem__ przypisanie indeksu i wycinka, X[key] = value, X[i:j] = sequence def __setitem__(self, key, value): pass # kod dla X[key] = value __delitem__ usuwanie indeksu i wycinka, del X[key], del X[i:j] __contains__ test przynależności item in X, item not in X (dowolny iterator)
# Wspomaganie kopiowania z modułem copy. __copy__(self) płytka kopia Y = copy.copy(X) __deepcopy__(self, memo) głęboka kopia (słownik memo) Y = copy.deepcopy(X, memo)
Przechwytywanie wywołań instancji realizuje metoda __call__. Dzięki temu klasy mogą emulować funkcje, ale z dodatkowymi możliwościami, jak zachowywanie stanu między wywołaniami.
class Printer: """Klasa reprezentująca obiekt wyświetlający.""" def __init__(self, counter=0): """Utwórz obiekt.""" self.counter = counter # licznik wywołań funkcji def __call__(self, *arguments, **keywords): """Obsługa wywołania.""" self.counter += 1 print("Wywołanie: {} {}".format(arguments, keywords)) X = Printer() X(1, 2) # Wywołanie: (1, 2) {} X(1, 2, x=3, y=4) # Wywołanie: (1, 2) {"x":3, "y":4} print(X.counter) # odczyt licznika wywołań funkcji
Czasem potrzebna jest znajomość liczby istniejących instancji danej klasy. Rozwiązaniem jest atrybut klasy, który na bieżąco zwiększa wartość o jeden przy tworzeniu instancji, a także zmniejsza wartość o jeden przy niszczeniu instancji. Atrybut klasy istnieje w przestrzeni nazw klasy, więc potrzebna jest odpowiednia kwalifikacja przy odnoszeniu się do niego. Bez kwalifikacji atrybut klasy nie jest widoczny w metodach klasy, listach składanych, wyrażeniach generatorów.
class Point: """Klasa dla punktów.""" counter = 0 # atrybut klasy # seq = list(counter + i for i in range(10)) # nie działa! # seq = list(Point.counter + i for i in range(10)) # potrzebna kwalifikacja def __init__(self, x=0, y=0): """Konstruktor punktu.""" self.x = x self.y = y # counter += 1 # nie działa! Point.counter += 1 # potrzebna kwalifikacja print("init: counter = {}".format(Point.counter)) def __str__(self): """Postać napisowa punktu.""" return "({}, {})".format(self.x, self.y) def __repr__(self): """Reprezentacja punktu.""" return "Point({}, {})".format(self.x, self.y) def __add__(self, other): # pt1 + pt2 """Dodawanie punktów jako wektorów.""" return Point(self.x + other.x, self.y + other.y) def __del__(self): """Destruktor punktu.""" Point.counter -= 1 print("del: counter={}".format(Point.counter)) print("counter = {}".format(Point.counter)) # na starcie counter=0 pt1 = Point(3.4, 5.6) # counter=1 pt2 = Point(4.5, 2.1) # counter=2 print("metoda __str__ {} {}".format(pt1, pt2)) print("metoda __add__ {}".format(pt1 + pt2)) # counter wzrasta do 3 i od razu wraca do 2 # Każda instancja ma referencję do swojej klasy. print(pt1.__class__) print(pt1.__dict__.keys()) # przestrzeń nazw instancji del pt1 # counter=1 del pt2 # counter=0 dir(Point) # przestrzeń nazw klasy Point.__dict__.keys() # przestrzeń nazw klasy
Każda klasa czy instancja ma atrybut __dict__, który jest słownikiem przestrzeni nazw obiektu (podobnie jak dla modułu). Klasy mają również atrybut __bases__ będący krotką ich klas nadrzędnych.
W Pythonie 3 nie ma metody __cmp__, tylko stosuje się rich comparisons. W Pythonie 2.6+ rich comparisons mają wyższy priorytet, niż metoda __cmp__, ale generalnie nie należy łączyć tych dwóch metod określających porównywanie obiektów.
http://stackoverflow.com/questions/20989750/why-python-need-rich-comparison
Why Python need rich comparison?
Ładne przykłady z NumPy i the SQLAlchemy project.
https://www.python.org/dev/peps/pep-0207/
PEP 207 - Rich Comparisons.
# https://docs.python.org/2/library/functools.html # W Pythonie 2.7 wprowadzono dekorator, który ułatwia tworzenie metod # specjalnych dla rich comparisons. # Klasa musi zdefiniować metodę __eq__() oraz jedną z metod: # __lt__(), __le__(), __gt__(), __ge__(). # Pozostałe metody zostaną wygenerowane przez dekorator. from functools import total_ordering @total_ordering class Student: # Zakładamy, że w klasie Student mamy atrybuty 'firstname' i 'lastname'. def __eq__(self, other): return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower())) def __lt__(self, other): return ((self.lastname.lower(), self.firstname.lower()) < (other.lastname.lower(), other.firstname.lower()))
https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations
https://www.pythonmorsels.com/when-to-use-notimplemented/
When to use NotImplemented.
>>> a = 3 >>> b = 3.0 >>> a == b True >>> a.__eq__(b) NotImplemented >>> b.__eq__(a) True >>> a + b 6.0 >>> a.__add__(b) NotImplemented >>> b.__radd__(a) 6.0 # Porównanie między obiektami zwraca NotImplemented, jeżeli pierwszy obiekt # nie wie jak wykonać operację na drugim obiekcie. Python wtedy przechodzi # do drugiego obiektu i próbuje wykonać daną operację. # # Typowo w Pythonie stosujemy 'duck typing', ale powyższa sytuacja jest # przykładem stosowania 'strong type-checking'.
Warto się zastanowić, czy w Pythonie potrzebny jest konstruktor domyślny, czyli dla klasy C konstruktor wywoływany bez argumentów C(). W C++ taki konstruktor jest uruchamiany przykładowo przy tworzeniu pustej tablicy obiektów i na ogół jest potrzebny. W Pythonie pustą tablicę zwykle wypełniamy obiektami None. Wbudowane typy danych zwykle mają taki konstruktor: int() i float() zwracają zero, list() i tuple() zwracają puste kolekcje, dict() zwraca pusty słownik. Wydaje się więc, że nie ma powodu, aby nie obsługować takiej sytuacji w klasach tworzonych przez użytkownika.