https://docs.python.org/3/tutorial/classes.html
W matematyce punkt na płaszczyźnie można zapisać za pomocą pary dwóch współrzędnych (x, y). W Pythonie naturalnym sposobem reprezentacji będzie para liczb typu float. Chcemy taką parę traktować jako jeden złożony obiekt. Przykładowym rozwiązaniem jest lista lub krotka, ale zwykle lepsze jest zdefiniowanie nowego typu, inaczej klasy. Składnia (dwukropek i wcięcia) jest taka jak dla innych wyrażeń złożonych w Pythonie. Nazewnictwo klas powinno być typu StudlyCups.
Instrukcja wykonywalna class tworzy obiekt klasy i przypisuje go do nazwy. Zakres instrukcji class staje się przestrzenią nazw atrybutów obiektu klasy. Atrybuty klasy udostępniają stan obiektu i jego zachowanie.
Wywołanie obiektu klasy tworzy za każdym razem nowy obiekt instancji klasy. Każdy obiekt instancji dziedziczy atrybuty klasy oraz otrzymuje własną przestrzeń nazw. Przypisania do atrybutów self w metodach tworzą atrybuty instancji (a nie klasy).
# Składnia. class NazwaKlasy: class_docstring # opcjonalnie instrukcje class NazwaKlasy(lista_klas_nadrzędnych): # dziedziczenie class_docstring # opcjonalnie instrukcje
class Point: # instrukcja tworząca obiekt klasy """Klasa odpowiadająca punktom na płaszczyźnie.""" pass # wymagana jakaś instrukcja point = Point() # tworzenie instancji klasy # Uwaga: nawiasy pokazują, że jest to wywołanie klasy. # Do punktu (instancji) przypisujemy atrybuty korzystając z notacji z kropką. point.x = 3.4 point.y = 5.6 x = 7.8 # Zmienne x i point.x to dwie różne wartości. # Instancja point jest osobną przestrzenią nazw. print("{} {}".format(x, point.x)) # 7.8 3.4 print(point) # heksadecymalne info # Obiekt może być parametrem funkcji w zwykły sposób. # Nazwa 'point' wewnątrz funkcji jest lokalna i nie koliduje # z globalną zmienną point. def print_point(point): """Wypisz punkt.""" print("({}, {})".format(point.x, point.y)) # Wywołujemy funkcję dla punktu. Do funkcji przekazujemy wartość # zmiennej 'point', czyli referencję do obiektu. print_point(point) # (3.4, 5.6)
Są sytuacje, w których trzeba dobrze rozumieć co to znaczy, że dwa obiekty są "te same" czy "takie same".
point1 = Point() # pierwszy punkt point1.x = 3 point1.y = 4 point2 = Point() # drugi punkt point2.x = 3 point2.y = 4 point1 == point2 # False, to nie są te same obiekty, inne id point3 = point1 # kopiujemy referencję point1 == point3 # True, to jest porównywanie referencji # Taki typ równości nazywa się shallow equality (płytka, powierzchowna równość). # Aby porównać zawartość obiektów (deep equality, głęboka równość), # możemy napisać funkcję. def same_point(point1, point2): """Porównaj punkty.""" return (point1.x == point2.x) and (point1.y == point2.y) same_point(point1, point2) # True, te same wartości x i y same_point(point1, point3) # True, oczywiste
Chcemy stworzyć klasę do reprezentowania prostokąta. Zakładamy dla prostoty, że boki prostokąta są równoległe do osi X i Y. Będziemy określać lewy dolny róg prostokąta, szerokość (width) i wysokość (height).
class Point: """Klasa dla punktów.""" pass class Rectangle: """Klasa dla prostokątów.""" pass box = Rectangle() box.width = 100.0 box.height = 200.0 box.corner = Point() # obiekt Point jest zagnieżdżony w obiekcie Rectangle box.corner.x = 0.0 # określamy atrybuty punktu box.corner.y = 0.0 # Nowe instancje klas mogą powstawać wewnątrz funkcji. def find_center(rect): # zwraca instancję punktu """Znajdź środek prostokąta.""" point = Point() point.x = rect.corner.x + rect.width / 2.0 point.y = rect.corner.y + rect.height / 2.0 return point # referencja do nowego obiektu center = find_center(box) print_point(center) # (50.0, 100.0) # Możemy zmieniać stan obiektu przez podstawienia jego atrybutów. # Zapiszemy to wewnątrz funkcji, która pozwala na powiększanie prostokąta. def grow_rect(rect, dw, dh): # funkcja zmienia oryginał """Powiększ prostokąt.""" rect.width = rect.width + dw rect.height = rect.height + dh
Często potrzebna jest nam operacja kopiowania obiektu, która czasem powiązana jest z tworzeniem aliasu. Istnieje moduł copy, który potrafi kopiować każdy obiekt.
import copy point1 = Point() point1.x = 3 point1.y = 4 point2 = copy.copy(point1) point1 == point2 # False, inne referencje same_point(point1, point2) # True, te same wartości x i y
Dla obiektów prostych jak Point, które nie mają zagnieżdzonych innych obiektów, zwykłe copy.copy() jest wystarczające (shallow copy). Ale dla obiektu Rectangle, który zawiera referencję do Point, copy.copy() nie robi tego, czego oczekujemy. Jeżeli stworzymy prostokąt rect1, a następnie za pomocą copy.copy() zrobimy kopię rect2, to okaże się, że rect1 i rect2 zawierają referencję do tego samego punktu (rect1.corner jest identyczny z rect2.corner). Rozwiązaniem jest głęboka kopia (deep copy). Wykorzystamy ją do przepisania funkcji grow_rect() tak, aby zwracała nowy obiekt klasy Rectangle, który jest w tym samym położeniu, ale ma nowe wymiary.
def grow_rect2(rect, dw, dh): # zwraca nowy obiekt """Zwróć powiększony prostokąt.""" import copy rect2 = copy.deepcopy(rect) rect2.width = rect2.width + dw rect2.height = rect2.height + dh return rect2