Klasy
Stronę tą wyświetlono już: 6703 razy
Najwyższa pora porozmawiać o tym, jak to w Pythonie deklaruje się klasy. Będzie to bardzo dziwaczna opowieść, albowiem sam Python jest dziwacznym językiem programowania. Mimo wszystko słowo kluczowe class na szczęście nawet w Pythonie jest stosowane do deklarowania własnych szablonów klas.
Metody magiczne klas
Magiczne metody są ściśle powiązane z funkcjami lub operatorami. Wszystkie nazwy magicznych metod klas zaczynają się i kończą podwójną dolną spacją (__).
Konstruktor i destruktor klas w Pythonie
Omówię pierwsze Pythonowe dziwactwa, czyli konstruktor i destruktor klasy:
- class Point2D:
- def __init__(self, x = None, y = None):
- self.x = float(input("Podaj współrzędną x: ")) if (x is None) else x
- self.y = float(input("Podaj współrzędną y: ")) if (y is None) else y
- def __del__(self):
- print("destruktor uruchomiony")
__init__ - konstruktor klasy. Konstruktora, jak i innych metod nie można przeciążać. Każda metoda klasy (w tym również konstruktor jak i destruktor muszą w swej deklaracji przyjmować jako pierwszy argument o nazwie self, który jest etykietą obiektu tej klasy. Co jeszcze bardziej dziwaczne jest, to to, że w konstruktorze deklaruje się pola klasy w sposób następujący: self.nazwa_pola_klasy.
__del__ - destruktor, uruchamiany gdy zostanie wykonane polecenie del na obiekcie klasy.
Oto przykład działania konstruktora i destruktora:
- p = Point2D() # tutaj tworzę pierwszy obiekt, a ponieważ nie podałem żadnych danych na wejście to program zapyta się o współrzędne x i y punktu
- p2 = Point2D(20, 30) # a tutaj tworzę obiekt, o podanych współrzędnych
- del p # tutaj usuwam obiekt, wywołując destruktor
- del p2 # to samo co powyżej dla drugiego obiektu
Wynik działania powinien być następujący:
Podaj współrzędną x: 10 Podaj współrzędną y: 20 destruktor uruchomiony destruktor uruchomiony
Rzutowanie
Oto przykład magicznej metody związanej z rzutowaniem obiektu na typ int za pomocą metody magicznej __int__:
- def __int__(self):
- return int((self.x * self.x + self.y * self.y) ** 0.5)
Powyższa metoda klasy Point2D oblicza i zwraca całkowitą wartość długości wektora. Oto przykład użycia tej metody:
- p = Point2D(10,0)
- print(int(p))
Wynik działania powyższego kodu:
10
Istnieje też możliwość rzutowania na typ float za pomocą metody magicznej __float__:
- def __float__(self):
- return (self.x * self.x + self.y * self.y) ** 0.5
Powyższa metoda klasy Point2D oblicza i zwraca zmiennoprzecinkową wartość długości wektora. Oto przykład użycia tej metody:
- p = Point2D(10,0)
- print(float(p))
Wynik działania powyższego kodu:
10.0
Teraz rzutowanie na str poprzez obsługę magicznej metody __str__:
- def __str__(self):
- return "Point2D(x={s.x}, y={s.y})".format(s = self)
Powyższa metoda klasy Point2D zwraca zmienną tekstową zawierającą współrzędne tego punktu. Oto przykład użycia tej metody:
- p = Point2D(10,0)
- print(p)
Wynik działania powyższego kodu:
Point2D(x=10, y=0)
Metoda magiczna __len__
Tę metodę już znamy z list, krotek, słowników i zbiorów. Teraz poznamy jak się ją deklaruje w własnej klasie:
- def __len__(self):
- return int(self)
Oto wywołanie tej metody za pomocą funkcji len:
- p = Point2D(10,0)
- print(len(p))
Wynik działania powyższego kodu:
10
Operatory porównania wartości
Obsługa operatorów klas w Pythonie również została zrzucona na barki metod magicznych. W tej części metody związane z obsługą operatorów porównania wartości, czyli:
- __cmp__ - najbardziej kompleksowy operator, obsługuje on wszystkie operatory porównania wartości czyli: <; <=; ==; !=;; >=; >;
- __eq__ - obsługuje operator równości ==;
- __ne__ - obsługuje operator nierówności !=;
- __lt__ - obsługuje operator mniejsze od <;
- __gt__ - obsługuje operator większe od >;
- __le__ - obsługuje operator mniejsze równe <=;
- __ge__ - obsługuje operator większe równe >=.
Oto przykład implementacji metod magicznych dla operatorów porównania wartości związanych z klasą Point2D:
- def __eq__(self, other):
- if isinstance(other, Point2D):
- return True if other.x == self.x and other.y == self.y else False
- else:
- return True if (float(self) == other) else False
- def __ne__(self, other):
- return not (self == other)
- def __lt__(self, other):
- return float(self) < float(other)
- def __gt__(self, other):
- return float(self) > float(other)
- def __le__(self, other):
- return not (self > other)
- def __gt__(self, other):
- return not (self < other)
Operatory arytmetyczne
Oto lista operatorów magicznych metod, które odpowiedzialne są za obsługę operatorów arytmetycznych:
- __add__ - dodawanie +;
- __sub__ - odejmowanie -;
- __mul__ - mnożenie *;
- __floordiv__ - dzielenie całkowite //;
- __div__ - dzielenie zmiennoprzecinkowe /;
- __mod__ - reszta z dzielenia %;
- __divmod__ - zwraca część całkowitą i resztę z dzielenia jako krotkę, pod tę metodą podpina się funkcja divmod;
- __pow__ - operator potęgowania **;
- __lshift__ - operator przesunięcia bitowego w lewo <<;
- __rshift__ - operator przesunięcia bitowego w prawo >>;
- __and__ - operator and;
- __or__ - operator or;
- __xor__ - operator xor
W klasie Point2D zastosowanie znajdą tylko niektóre z powyższych operatorów, których przykład implementacji poniżej zamieszczam:
- def __add__(self, other):
- if isinstance(other, Point2D):
- return Point2D(self.x + other.x, self.y + other.y)
- else:
- return Point2D(self.x + float(other), self.y + float(other))
- def __sub__(self, other):
- if isinstance(other, Point2D):
- return Point2D(self.x - other.x, self.y - other.y)
- else:
- return Point2D(self.x - float(other), self.y - float(other))
- def __mul__(self, other):
- if isinstance(other, Point2D):
- return self.x * other.x + self.y * other.y
- else:
- return Point2D(self.x * float(other), self.y * float(other))
Odwrócone operatory arytmetyczne
Operatory arytmetyczne wcześniej opisane obsługują tylko przypadek następującej postaci:
- p = Point2D(10,20)
- p = p * 10
Nie obsługują natomiast takiego przypadku:
- p = Point2D(10,20)
- p = 10 * p
ale, nie ma co płakać, albowiem Pythoniści znaleźli rozwiązanie tego problemu, którym są następujące metody magiczne:
- __radd__ - dodawanie +;
- __rsub__ - odejmowanie -;
- __rmul__ - mnożenie *;
- __rfloordiv__ - dzielenie całkowite //;
- __rdiv__ - dzielenie zmiennoprzecinkowe /;
- __rmod__ - reszta z dzielenia %;
- __rpow__ - operator potęgowania **;
- __rlshift__ - operator przesunięcia bitowego w lewo <<;
- __rrshift__ - operator przesunięcia bitowego w prawo >>;
- __rand__ - operator and;
- __ror__ - operator or;
- __rxor__ - operator xor
W klasie Point2D zastosowanie znajdą tylko niektóre z powyższych operatorów, których przykład implementacji poniżej zamieszczam:
- def __radd__(self, other):
- if isinstance(other, Point2D):
- return Point2D(self.x + other.x, self.y + other.y)
- else:
- return Point2D(self.x + float(other), self.y + float(other))
- def __rsub__(self, other):
- if isinstance(other, Point2D):
- return Point2D(other.x - self.x, other.y - self.y)
- else:
- return Point2D(float(other) - self.x, float(other) - self.y)
- def __rmul__(self, other):
- if isinstance(other, Point2D):
- return self.x * other.x + self.y * other.y
- else:
- return Point2D(self.x * float(other), self.y * float(other))
Operatory arytmetyczne z podstawieniem
Przydałoby się jeszcze obsłużyć operatory arytmetyczne z podstawieniem, czyli dla przykładu takie:
- p = Point2D(10,20)
- p += p
I w tym przypadku Pythoniści znaleźli rozwiązanie tego problemu, którym są następujące metody magiczne:
- __iadd__ - dodawanie z podstawieniem +=;
- __isub__ - odejmowanie z podstawieniem -=;
- __imul__ - mnożenie z podstawieniem *=;
- __ifloordiv__ - dzielenie całkowite z podstawieniem //=;
- __idiv__ - dzielenie zmiennoprzecinkowe z podstawieniem /=;
- __imod__ - reszta z dzielenia z podstawieniem %=;
- __ipow__ - operator potęgowania z podstawieniem **=;
- __ilshift__ - operator przesunięcia bitowego w lewo z podstawieniem <<=;
- __irshift__ - operator przesunięcia bitowego w prawo z podstawieniem >>=;
- __iand__ - operator and z podstawieniem &=;
- __ior__ - operator or z podstawieniem |=;
- __ixor__ - operator xor z podstawieniem ^=
W klasie Point2D zastosowanie znajdą tylko niektóre z powyższych operatorów, których przykład implementacji poniżej zamieszczam:
- def __iadd__(self, other):
- if isinstance(other, Point2D):
- self.x += other.x
- self.y += other.y
- else:
- self.x += float(other)
- self.y += float(other)
- return self
- def __isub__(self, other):
- if isinstance(other, Point2D):
- self.x -= other.x
- self.y -= other.y
- else:
- self.x -= float(other)
- self.y -= float(other)
- return self
- def __imul__(self, other):
- self.x *= float(other)
- self.y *= float(other)
- return self
Operatory jednoargumentowe
Oto lista jednoargumentowych metod obsługujących operatory i niektóre funkcje matematyczne:
- __pos__ - jednoargumentowy operator znaku +;
- __neg__ - jednoargumentowy operator znaku -;
- __abs__ - implementacja zachowania funkcji abs;
- __invert__ - implementacja operatora inwersji ~;
- __floor__ - implementacja zachowania funkcji math.floor;
- __ceil__ - implementacja zachowania funkcji math.ceil;
- __trunc__ - implementacja zachowania funkcji math.trunc;
Przykład implementacji co niektórych magicznych metod wyżej wymienionych dla klasy Point2D:
- def __pos__(self):
- return Point2D(self.x, self.y)
- def __neg__(self):
- return Point2D(- self.x, - self.y)
- def __abs__(self):
- return Point2D(abs(self.x), abs(self.y))
Atrybuty klasy
Kolejnym Pythonowym dziwactwem są atrybuty klasy, które są związane z nazwą klasy a nie z konkretnym obiektem tejże klasy. Zanim jednak pokażę jak tworzy się atrybuty klasy, to najpierw utworze klasę Matrix_tr, która będzie opisywała macierz transformacji:
- class Matrix_tr:
- def __init__(self, m11 = 1, m12 = 0, m21 = 0, m22 = 1, dx = 0, dy = 0, alpha = None):
- if alpha is None: # if alpha angle is set on None value
- self.m11 = m11
- self.m12 = m12
- self.m21 = m21
- self.m22 = m22
- else: # in other case calculate m11 - m22 using alpha angle
- self.m11 = mt.cos(alpha)
- self.m12 = -mt.sin(alpha)
- self.m21 = - self.m12 # this should be sin of angle alpha
- self.m22 = self.m11 # this should be cos of angle alpha
- self.dx = dx # this is an offset for x
- self.dy = dy # this is an offset for y
- def __mul__(self, other): # multiplication operator (*)
- if isinstance(other, Point2D): # for object of Point2D class
- return Point2D(other.x * self.m11 + other.y * self.m12 + self.dx, other.x * self.m21 + other.y * self.m22 + self.dy)
- if isinstance(other, Matrix_tr): # for object of Matrix_tr class
- return Matrix_tr(self.m11 * other.m11 + self.m12 * other.m21, self.m11 * other.m12 + self.m12 * other.m22, self.m21 * other.m11 + self.m22 * other.m21, self.m21 * other.m12 + self.m22 * other.m22, self.m11 * other.dx + self.m12 * other.dy +
- self.dx, self.m21 * other.dx + self.m22 * other.dy + self.dy)
- def __str__(self):
- return "| M11 = {m.m11:+{l}.5f} M12 = {m.m12:{l}.5f} dx = {m.dx:+{l}.5f} |\n| M21 = {m.m21:+{l}.5f} M22 = {m.m22:+{l}.5f} dy = {m.dy:+{l}.5f} |\n| M31 = {m31:+{l}.5f} M32 = {m32:+{l}.5f} {m33:+{l}.5f} |".format(m = self, l = 10, m31 = 0, m32 = 0, m33 = 1)
Teraz wykorzystam klasę Matrix_tr jako atrybut klasy Point2D:
- class Point2D:
- tr = Matrix_tr() # atrybut klasy Point2D
Dostęp atrybutów klasy można osiągnąć na dwa następujące sposoby:
- print(Point2D.tr)
- p = Point2D(100,200)
- print("Z poziomu obiektu klasy:\n",p.tr)
Wynik działania:
| M11 = +1.00000 M12 = 0.00000 dx = +0.00000 | | M21 = +0.00000 M22 = +1.00000 dy = +0.00000 | | M31 = +0.00000 M32 = +0.00000 +1.00000 | Z poziomu obiektu klasy | M11 = +1.00000 M12 = 0.00000 dx = +0.00000 | | M21 = +0.00000 M22 = +1.00000 dy = +0.00000 | | M31 = +0.00000 M32 = +0.00000 +1.00000 |
Co ciekawe, jeżeli zrobię coś takiego:
- import math as mt
- p = Point2D(100,200)
- p.tr = Matrix_tr(alpha = mt.pi / 3)
- print("Z poziomu definicji klasy:\n",Point2D.tr)
- print("Z poziomu obiektu klasy:\n", p.tr)
Wynik działania:
Z poziomu definicji klasy: | M11 = +1.00000 M12 = 0.00000 dx = +0.00000 | | M21 = +0.00000 M22 = +1.00000 dy = +0.00000 | | M31 = +0.00000 M32 = +0.00000 +1.00000 | Z poziomu obiektu klasy: | M11 = +0.50000 M12 = -0.86603 dx = +0.00000 | | M21 = +0.86603 M22 = +0.50000 dy = +0.00000 | | M31 = +0.00000 M32 = +0.00000 +1.00000 |
Jak widać, na powyższym przykładzie, zmiana wartości atrybutu zawartego w danym obiekcie klasy nie zmienia wartości atrybutu definicji klasy.
Własne metody
Tworzenie własnych metod klasy nie różni się znacząco deklaracji metod magicznych. Utwórzmy zatem metodą klasy Point2D w następujący sposób:
- def draw(self):
- print(Point2D.tr * self)
Powyższa metoda wykorzystuje atrybut klasy, którym jest macierz transformacji. Macierz ta przemnożona lewostronni przez punkt zwraca punkt, który może zostać obrócony i przesunięty (w zależności od ustawień macierzy transformacji Point2D.tr. Oto przykład użycia tej metody:
- import math as mt
- p = Point2D(100,200)
- p.draw()
- Point2D.tr = Matrix_tr(dx = 10, dy = 10)
- print("Po przesunięciu:")
- p.draw()
- Point2D.tr = Matrix_tr(alpha = mt.pi / 3) * Point2D.tr
- print("Po przesunięciu i obróceniu o 60 stopni:")
- p.draw()
Wynik działania:
Point2D(x=100, y=200) Po przesunięciu: Point2D(x=110, y=210) Po przesunięciu i obrócenie o 60 stopni: Point2D(x=-126.86533479473209, y=200.2627944162883)
Jak widać macierz transformacji zapisana jako atrybut klasy Point2D umożliwia przemieszczanie oraz obracanie wszystkich punktów, które zostaną utworzone tak przed, jak i po zmianie macierzy transformacji.
Wykorzystanie klasy Point2D w grafice żółwia
Po lekkiej przeróbce klasy Point2D polegającej na zmianie metody draw można wykorzystać tę klasę do rysowania w omawianej już wcześniej grafice żółwia. Oto kod programu:
- #!/usr/bin/env python
- # coding: utf-8
- import math as mt
- import turtle as tr
- class matrix_tr:
- def __init__(self, m11 = 1, m12 = 0, m21 = 0, m22 = 1, dx = 0, dy = 0, alpha = None):
- if alpha is None: # if alpha angle is set on None value
- self.m11 = m11
- self.m12 = m12
- self.m21 = m21
- self.m22 = m22
- else: # in other case calculate m11 - m22 using alpha angle
- self.m11 = mt.cos(alpha)
- self.m12 = -mt.sin(alpha)
- self.m21 = - self.m12 # this should be sin of angle alpha
- self.m22 = self.m11 # this should be cos of angle alpha
- self.dx = dx # this is an offset for x
- self.dy = dy # this is an offset for y
- def __mul__(self, other): # multiplication operator (*)
- if isinstance(other, Point2D): # for object of Point2D class
- return Point2D(other.x * self.m11 + other.y * self.m12 + self.dx, other.x * self.m21 + other.y * self.m22 + self.dy)
- if isinstance(other, matrix_tr): # for object of matrix_tr class
- return matrix_tr(self.m11 * other.m11 + self.m12 * other.m21, self.m11 * other.m12 + self.m12 * other.m22, self.m21 * other.m11 + self.m22 * other.m21, self.m21 * other.m12 + self.m22 * other.m22, self.m11 * other.dx + self.m12 * other.dy + self.dx, self.m21 * other.dx + self.m22 * other.dy + self.dy)
- def __str__(self):
- return "| M11 = {m.m11:+{l}.5f} M12 = {m.m12:{l}.5f} dx = {m.dx:+{l}.5f} |\n| M21 = {m.m21:+{l}.5f} M22 = {m.m22:+{l}.5f} dy = {m.dy:+{l}.5f} |\n| M31 = {m31:+{l}.5f} M32 = {m32:+{l}.5f} {m33:+{l}.5f} |".format(m = self, l = 10, m31 = 0, m32 = 0, m33 = 1)
- class Point2D:
- tr = matrix_tr(alpha = mt.pi / 2)
- def __init__( self, x = None, y = None): # konstruktor
- if x is None:
- self.x = float(input("Podaj wartość współrzędnej x: "))
- else:
- self.x = x
- if y is None:
- self.y = float(input("Podaj wartość współrzędnej y: "))
- else:
- self.y = y
- self.wtr = matrix_tr()
- def __float__(self): # rzutowanie na float
- return (self.x * self.x + self.y * self.y) ** 0.5
- def __int__(self):
- return int((self.x * self.x + self.y * self.y) ** 0.5)
- def __len__(self):
- return int(self)
- def __add__(self, other): # dodawanie
- return Point2D( self.x + other.x, self.y + other.y)
- def __sub__(self, other): # odejmowanie
- return Point2D( self.x - other.x, self.y - other.y)
- def __mul__(self, other): # mnożenie
- if isinstance(other, Point2D):
- return self.x * other.x + self.y * other.y
- if isinstance(other, int) or isinstance(other, float):
- return Point2D(self.x * other, self.y * other)
- return None
- def __radd__(self, other): # dodawanie
- return Point2D( self.x + other.x, self.y + other.y)
- def __rsub__(self, other): # odejmowanie
- return Point2D( other.x - self.x, other.y - self.y)
- def __rmul__(self, other): # mnożenie
- if isinstance(other, Point2D):
- return self.x * other.x + self.y * other.y
- if isinstance(other, int) or isinstance(other, float):
- return Point2D(self.x * other, self.y * other)
- return None
- def __iadd__(self, other): # dodawanie
- return Point2D( self.x + other.x, self.y + other.y)
- def __isub__(self, other): # odejmowanie
- return Point2D( other.x - self.x, other.y - self.y)
- def __imul__(self, other): # mnożenie
- if isinstance(other, Point2D):
- return self.x * other.x + self.y * other.y
- if isinstance(other, int) or isinstance(other, float):
- return Point2D(self.x * other, self.y * other)
- return None
- def __div__(self, other): # dzielenie
- if isinstance(other, int) or isinstance(other, float):
- return Point2D(self.x / other, self.y / other)
- def __str__(self): # rzutowanie na stringa
- return "Point2D: x={x}; y={y}".format(x = self.x, y = self.y)
- def draw(self):
- p = self.wtr * Point2D.tr * self
- tr.goto(p.x, p.y)
- def drawfu(function, xmin, xmax):
- x = xmin
- for x in range(xmin, xmax):
- y = eval(function)
- tr.pencolor(((x - xmin) / (xmax - xmin), 1 - (x - xmin) / (xmax - xmin), 0))
- p = Point2D(x, y)
- p.draw()
- if __name__ == "__main__":
- while True:
- tr.reset()
- tr.pensize(5)
- sc = tr.getscreen()
- sc.bgcolor(0,0,0)
- for j in range(3):
- for i in range(6):
- Point2D.tr = matrix_tr(dx = tr.pos()[0], dy = tr.pos()[1], alpha = i * mt.radians(60) + j * mt.radians(120))
- drawfu("20 * mt.sin(mt.radians(x * 3.6 * 5)) * mt.sin(mt.radians(x * 1.8))", 0, 100)
Wynikiem działania programu będzie powtarzanie animacji pokazanej na poniższym nagraniu.

Tytuł:
Python w zadaniach. Programowanie dla młodzieży. Poziom podstawowy
Autor:
Urszula Wiejak, Adrian Wojciechowski

Tytuł:
Python i praca z danymi. Przetwarzanie, analiza, modelowanie i wizualizacja. Wydanie III
Autor:
Avinash Navlani, Armando Fandango, Ivan Idris

Tytuł:
Black Hat Python. Język Python dla hakerów i pentesterów. Wydanie II
Autor:
Justin Seitz, Tim Arnold

Tytuł:
Python z życia wzięty. Rozwiązywanie problemów za pomocą kilku linii kodu
Autor:
Lee Vaughan

Tytuł:
Python dla nastolatków. Projekty graficzne z Python Turtle
Autor:
Krzysztof Łos

Tytuł:
Python i Excel. Nowoczesne środowisko do automatyzacji i analizy danych
Autor:
Felix Zumstein

Tytuł:
Python dla testera
Autor:
Piotr Wróblewski

Tytuł:
Python 3. Projekty dla początkujących i pasjonatów
Autor:
Adam Jurkiewicz

Tytuł:
Machine learning, Python i data science. Wprowadzenie
Autor:
Andreas C. Müller, Sarah Guido

Tytuł:
Python na maturze. Rozwiązania i analiza wybranych zadań programistycznych
Autor:
Roland Zimek