self, other
Transkrypt
self, other
Podstawy programowania Python – wykład 10 Definiowanie metody inicjalizującej class Nazwa_Klasy: def __init__(self, param_list): block ● Python wywołuje tę metodę zaraz po tym, jak interpreter utworzy egzemplarz klasy Nazwa_Klasy. ● Parametr self odwołuje się do nowego egzemplarza. ● Instrukcje w block mogą odwoływać się do zmiennych. ● ● Lista param_list reprezentuje zero lub więcej parametrów oddzielonych przecinkami. block jest wciętym blokiem instrukcji. Uwagi ● ● Metoda __init__() jest umownie definiowana w klasie jako pierwsza. self odnosi się do nowo utworzonego egzemplarza (w przypadku innych metod self odnosi się do egzemplarza, którego metoda została wywołana). ● Metoda __init__() powinna zwracać None. ● Instrukcja: egz = Nazwa_Klasy() jest wewnetrznie odpowiednikiem następującej: Point.__init__(egz) Przykład 1 class Point: def __init__(self, x = 0, y = 0): self.x = x self.y = y pt1 = Point(3,9) pt2 = pt1 pt3 = pt1 print(id(pt1), id(pt2), id(pt3)) Usuwanie egzemplarza ● ● ● ● ● Python w sposób automatyczny usuwa niewykorzystywane (osierocone) egzemplarze przez proces odśmiecania i zwolnienia zajmowanego obszaru pamięci. Klasa może jednak poslużyć się specjalną metodą __del__(), która zwana jest destruktorem. Jest ona wywoływana wtedy, gdy dany egzemplarz ma być usunięty. Metoda ta może być wykorzystana do oczyszczania wszelkich zasobów pamięci używanych przez egzemplarz. Metoda ta jest rzadko stosowana w praktyce, ponieważ nie wiadomo czy będzie wywołana w okreslonym odstępie czsu (może zdarzyć się, że nie będzie wywołana wcale). Przykład 2 class Point: def __init__(self, x = 0, y = 0): self.x = x self.y = y def __del__(self): nazwa = self.__class__.__name__ print(nazwa, 'zniszczono') pt1 = Point(3,9) pt2 = pt1 pt3 = pt1 print(id(pt1), id(pt2), id(pt3)) del(pt1); del(pt2); del pt3 Tworzenie nieformalnej reprezentacji łańcuchowej def __str__(self): block ● ● Python wywołuje tę metodę wtedy, gdy egzemplarz jest używany jako argument funkcji str() lub print. Wielkość self dotyczy egzemplarza, dla którego jest wywoływana metod __str__(). ● Zwracana wartość musi być obiektem łańcuchowym. ● Wielkość block jest wciętym blokiem instrukcji. Tworzenie formalnej reprezentacji łańcuchowej def __repr__(self): block ● ● Python wywołuje tę metodę wtedy, gdy egzemplarz jest używany jako argument funkcji repr(), albo gdy jest on otoczony odwrotnymi apostrofami. Wielkość self dotyczy egzemplarza, dla którego jest wywoływana metod __repr__(). ● Zwracana wartość musi być obiektem łańcuchowym. ● Wielkość block jest wciętym blokiem instrukcji. UWAGI ● Formalna reprezentacja łańcuchowa egzemplarza jest używana głównie przy wyszukiwaniu błędów w fazie uruchamiania programu, zatem ważne jest, by metoda __repr__() zwracała łańcuch zawierający pełną i jednoznaczną informację. ● Jeśli nie jest możliwe uzyskanie poprawnego wyrażenia Pythona za pomocą metody __repr__() powinna ona zwracać łańuch w postaci ''<...opis...>''. Opis powinien zawierać szczegółową informację o egzemplarzu. Przykład 3 class Point: def __init__(self, x = 0, y = 0): self.x = x self.y = y def __str__(self): return '(%g,%g)' %(self.x, self.y) def __repr__(self): return 'Point(%s,%s)' %(self.x, self.y) ... print(str(pt1)) print(repr(pt1)) Określanie prawdziwości egzemplarza def __nonzero__(self): block ● ● Python wywołuje tę metodę wtedy, gdy egzemplarz zostanie użyty w wyrażeniu logicznym. Wielkość self dotyczy egzemplarza, dla którego jest wywoływana metod __nonzero__(). ● Zwracana wartość musi być 1 (prawda) lub 0 (fałsz). ● Wielkość block jest wciętym blokiem instrukcji. ● Jeśli metoda nie jest zdefiniowana, Python wywołuje metodę __len__(). Jeśli klasa nie definiuje obydwu metod, wszystkie egzemplarze będą traktowane jako prawdziwe. Przykład 4 class Point: ... def __nonzero__(self): if self.x == 0 and self.y == 0: return 0 else: return 1 ... for pt in [pt1, pt2, pt3]: if pt: print(pt, 'is true') else: print(pt, 'is false') Porównywanie egzemplarzy def __comp__(self, other): block ● ● ● ● ● __comp__() jest jedną z metod z tabeli 1. Python wywołuje tę metodę wtedy, gdy egzemplarz zostanie użyty w wyrażeniu porównującym. Wielkość self dotyczy egzemplarza, dla którego jest wywoływana metod __comp__(), other jest egzemplarzem, z którym dany egzemplarz jest porównywany. Zwracana wartość może być dowolną wartością, lecz w kontekście logicznym powinna to być wartość logiczna 1 (prawda) lub 0 (fałsz); w przeciwny przypadku wystapi wyjątek TypeError. Wielkość block jest wciętym blokiem instrukcji. Tabela 1 Metoda __lt__(self,other) __le__(self,other) __gt__(self,other) __ge__(self,other) __eq__(self,other) __ne__(self,other) Wynik self < self <= self > self >= self == self != other other other other other other Przykład 5 from math import hypot class Point: ... def __lt__(self, other): return hypot(self.x,self.y) < hypot(other.x,other.y) def __le__(self, other): return hypot(self.x,self.y) <= hypot(other.x,other.y) def __gt__(self, other): return hypot(self.x,self.y) > hypot(other.x,other.y) def __ge__(self, other): return hypot(self.x,self.y) >= hypot(other.x,other.y) def __eq__(self, other): return hypot(self.x,self.y) == hypot(other.x,other.y) def __ne__(self, other): return hypot(self.x,self.y) != hypot(other.x,other.y) UWAGI ● ● Podane metody porównują egzemplarze klasy Point korzystając z odległości geometrycznej od początku układu współrzędnych (0,0). Funkcja math.hypot(x,y) oblicza odległość zgodnie z regułami geometrii euklidesowej, która równa się math.sqrt(x*x+y*y). Metody operatorów dwustronnych Metoda Wynik __add__(self, other) __sub__(self, other) __mul__(self, other) __div__(self, other) __mod__(self, other) __divmod__(self, other) __pow__(self, other) __lshift__(self, other) __rshift__(self, other) __and__(self, other) __or__(self, other) __xor__(self, other) self + other self - other self * other self / other self % other divmod(self, other) self ** other self << other self >> other self & other self | other self ^ other Metody przypisań przyrostowych Metoda Wynik __iadd__(self, other) __isub__(self, other) __imul__(self, other) __idiv__(self, other) __imod__(self, other) __ipow__(self, other) __ilshift__(self, other) __irshift__(self, other) __iand__(self, other) __ior__(self, other) __ixor__(self, other) self self self self self self self self self self self += other -= other *= other /= other %= other **= other <<= other >>= other &= other |= other ^= other Przykład 6 class Point: ... def __add__(self, other): if hasattr(other, '__class__') and other.__class__ is Point: return Point(self.x + other.x, self.y + other.y) else: return Point(self.x + other, self.y + other) def __pow__(self, other): return Point(self.x ** other,self.y ** other) pt1 = Point(0,0) pt2 = Point(2,2) pt = pt1 + pt2 print(pt) print(pt2 + 10) print(pt2**2) Dziedziczenie class NazwaKlasyPotomnej(NazwaKlasyBazowej): <instrukcja_1> . . . <instrukcja_N> UWAGI ● ● ● Nazwa NazwaKlasyBazowej musi być zdefiniowana w zasięgu zawierającym definicję klasy pochodnej. Zamiast nazwy klasy bazowej dopuszcza się również wyrażenie. Jeśli poszukiwany atrybut (lub metoda) nie jest znajdowany w klasie, poszukiwany jest w klasie bazowej. Ta zasada stosowana jest rekurencyjnie w przypadku, gdy klasa bazowa jest pochodną innej. Klasy pochodne mogą przesłaniać metody ich klas bazowych. (Uwaga dla programistów C++: wszystkie metody w Pythonie zachowują sie jak wirtualne.) Dziedziczenie wielorakie class NazwaKlasyPochodnej(Bazowa1, Bazowa2, ...): <instrukcja_1> . . . <instrukcja_N> UWAGI ● ● Zasada ,,najpierw w głąb, potem na prawo''. Jeśli atrybut nie zostanie znaleziony w klasie NazwaKlasyPochodnej, zostanie poszukany w Bazowa1, a potem (rekurencyjnie) w klasach bazowych klasy Bazowa1 i jeśli tam nie zostanie znaleziony, poszukiwanie zostanie przeniesione do klasy Bazowa2. Nieograniczone użycie dziedziczenia wielorakiego może w oczywisty sposób stać się koszmarem w procesie pielęgnacji oprogramowania, zwłaszcza że w Pythonie unikanie konfliktów nazw opiera się na umowie. Jednym z bardziej znanych przykładów problemu z dziedziczeniem wielorakim jest sytuacja gdy dwie klasy bazowe dziedziczą z tej samej klasy-przodka. Nie wiadomo jak bardzo taka semantyka jest użyteczna w połączeniu z faktem, że mamy pojedynczą kopię ,,zmiennych konkretu'' lub atrybutów danych wspólnej klasy bazowej.