.. _Klasy: Klasy i ich obiekty ------------------- Przykładowa klasa """"""""""""""""""""""""""" Klasa to przepis na kawałek kodu zrobiony z funkcji i zmiennych. Oto przykład w języku C++: .. code-block:: cpp :linenos: class Vec3 { public: float x = 0, y = 0, z = 0; Vec3(const Vec3 & v) { x = v.x; y = v.y; z = v.z; } double length() { return sqrt(x*x + y*y + z*z); } double distance_to(const Vec3 & v) { return sqrt((x - v.x)*(x - v.x) + (y - v.y)*(y - v.y) + (z - v.z)*(z - v.z)); } } ... a to w języku Python: .. code-block:: python :linenos: :emphasize-lines: 3,4,5,6 class Vec3: def __init__(self,x = 0, y = 0, z = 0) : self.__x = x self.__y = y self.__z = z def length(self) : return math.sqrt(self.__x*self.__x + self.__y*self.__y + self.__z*self.__z) def distance_to(self, v) : return math.sqrt( (self.__x - v.x)*(self.__x - v.x) + (self.__y - v.y)*(self.__y - v.y) + (self.__z - v.z)*(self.__z - v.z) ) Zmienne, będące składowymi klasy nazywamy **polami** lub **własnościami** (ang. *properties*); funkcje zaś **metodami**. W powyższym przykładzie mamy pola ``x``, ``y`` i ``z`` oraz np metodę ``distance_to()``. Przeciążenie """"""""""""""""""""""""""" Z metodami jest jak z konstruktorem: w C++ i w JAVA jedna klasa może mieć kilka o tej samej nazwie, nazywane je **przeciążonymi** (`overloaded`). Metody te muszą różnić się listą argumentów. W Pythonie przeciążenie udajemy, stosując ``*args``. Pól nie można przeciążać. Konstruktor klasy """""""""""""""""" Klasa może mieć wiele różnych pól i metod; szczególną rolę ma metoda zwana **konstruktorem**. **Konstruktor** to funkcja wywoływana dokładnie raz: w momencie tworzenia obiektu. Może robić przeróżne rzeczy, ale najczęściej służy do inicjowania zmiennych. W Pythonie (linie 3-6 powyżej) ma szczególne znaczenie: nie tylko inicjuje zmienne, ale je tworzy. Konstruktor nie może zwracać wartości. **Obiekty** danej klasy tworzymy, używając nazwy klasy: .. code-block:: python :linenos: v1 = Vec3() # Argumenty domyślne! v2 = Vec3(1.2, 0.9, 67.8) W C++, podobnie jak w JAVA, konstruktor musi nazywać się tak, jak nazwa klasy i może być ich kilka. Zauważ, że nie ma zadeklarowanego typu wartości zwracanej. W Pythonie można stworzyć tylko jedną funkcję o danej nazwie, w szczególności tylko jedna może nazywać się ``__init__()``, dlatego konstruktor w klasie może być tylko jeden. Aby udostępnić tworzenie obiektów z różnych danych wejściowych, trzeba posłużyć się zmienną listą argumentów .. literalinclude:: Vec3a.py :language: python :linenos: :lines: 1-13 Konstruktory poniżej nazywamy :term:`konstruktorem domyślnym ` oraz :term:`konstruktorem kopiującym `. Ten ostatni służy do tworzenia głębokiej kopii istniejącego obiektu: .. code-block:: python :linenos: v1 = Vec3() # Argumenty domyślne - konstruktor domyślny v2 = Vec3(v1) # konstruktor kopiujący Pola i metody """""""""""""""""" Do elementów składowych klasy, tj. pól i metod, odwołujemy się *"po kropce"*. Dla przykładu, poszukajmy wersora wskazującego na :math:`v_{1} + v_{2}`, gdzie :math:`v_{1} = (1.2, 0.9, 6.8)` oraz :math:`v_{1} = (-4.2, 1.2, -2.8)`: .. code-block:: python v1 = Vec3(1.2, 0.9, 6.8) v2 = Vec3(-4.2, 1.2, -2.8) v = Vec3() v.x = v1.x + v2.x v.y = v1.y + v2.y v.z = v1.z + v2.z d = v.length() v.x /= d v.y /= d v.z /= d .. note:: Pisząc klasę warto z góry przewidzieć, jakie metody będą potrzebne jej użytkownikowi. W przypadku klady ``Vec3`` przydały by się ``add()``, ``sub()``, ``mul()``, ``norm()``, ``dot_product()`` i ``vec_product()``. Mając je, można by np. na podstawie :math:`v_{1}` i :math:`v_{2}` łatwo zbudować ortonormalny układ wektorów: .. math:: \begin{align} v_x & = & | |v_1| + |v_2| |\\ v_y & = & | |v_1| - |v_2| |\\ v_z & = & v_x \times v_y \end{align} .. code-block:: python v1n = Vec3(v1) v1n.norm() v2n = Vec3(v2) v2n.norm() vx = Vec3(v1n) vx.add(v2n) vy = Vec3(v1n) vy.sub(v2n) vx.norm() vy.norm() vz = vx.vec_product(vy) .. todo:: Napisz klasę ``Vec3``, tak aby powyższe przykłady działały poprawnie Metody `set()` i `get()` """"""""""""""""""""""""""" Bezpośredni zapis i odczyt pól klasy jest bardzo wygodny; korzystamy z nich jak ze zwykłej zmiennej. Rozwiązanie takie jest mało elastyczne. Zazwyczaj stosuje się do tego celu odpowiednie funkcje, zwane odpowiednio *setter* i *getter* .. code-block:: python def set_x(self,x) : self.x = x def get_x(self) : return self.x Stosowanie funkcji ``set()`` i ``get()`` nie jest obowiązkowe w programowaniu obiektowym. To jedynie pewna często przyjmowana konwencja. Kontrola dostępu do składowych klasy """""""""""""""""""""""""""""""""""" Niezmiernie ważnym elementem programów obiektowych jest kontrola dostępu do pól i metod. Generalnie moglibyśmy wyobrazić sobie następujące możliwości kontroli: - dla obiektu samego sobie - dla innych obiektów tej samej klasy - dla obiektów potomnych - dla wszystkich innych A jak jest naprawdę? - dany obiekt ma zawsze pełen dostęp do wszystkich swoich składowych. - Dostęp ustalany jest dla **klasy** a nie dla **obiektu**, co oznacza, że dwa obiekty tej samej klasy mogą nawzajem odczytywać wszystkie swoje dane! - obiekty potomne widzą :term:`publiczne składowe `, :term:`składowe chronione `, :term:`składowe prywatne ` nie są dostępne - wszystkie pozostałe elementy programu widzą jedynie :term:`składowe prywatne ` Język Python nie ma niestety *ścisłej* kontroli dostępu. Przyjmuje się jednak, że metoda lub pole, którego nazwa zaczyna się od podwójnego podkreślenia ('__'), jest prywatne (**prywatną składową klasy**). Składowe (czyli pola i metody) chronione zaczynają się od pojedynczego podkreślenia ('__') Różnicę w działaniu pól prywatnych i publicznych w Pythonie ilustruje poniższy przykład. Pola chronione zaprezentowane zostaną po wyjaśnieniu dziedziczenia klas w kolejnym rozdziale. .. literalinclude:: try_private.py :language: python :linenos: :lines: 1-23 W powyższym przykładzie tylko obiekt klasy ``TajnePoleW`` (na przykład ``d``) może odczytać pole ``c.__w``, nikt inny. Faktycznie, próba *"nieuprawnionego"* odczytania składowej prywatnej zakończy się wyrzuceniem wyjątku. Ograniczenie to nie jest jednak sztywne i można je obejść: .. literalinclude:: read_private.py :language: python :linenos: :emphasize-lines: 9 Teraz zatem możemy dopisać kolejny kawałek klasy ``Vec3``: .. literalinclude:: Vec3b.py :language: python :linenos: :lines: 1-25