Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 8809 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.

Layout wykonany przez autora strony, wszelkie prawa zastrzeżone. Jakiekolwiek użycie części lub całości grafik znajdujących się na tej stronie bez pisemnej zgody jej autora surowo zabronione.