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
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:
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)
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.