Poprzedni wykład     Lista wykładów    

Klasy

Najkrócej mówiąc, klasa to definicja obiektu, obiekt z kolei to komputerowy model realnie istniejącego bytu. Definiowanie klasy Dziedziczenie Atrybuty klasy i składowe prywatne

Definiowanie klasy

Klasy można definiować w trybie interaktywnym, zazwyczaj są one jednak definiowane w plikach źródłowych (modułach). Najkrótsza (i najprostsza) definicja klasy wygląda tak:

class Pusta:
    pass
Klasa ta jest całkowicie nieprzydatna, ponieważ nie zawiera konstruktora, to nie można utworzyć obiektu typu Pusta.

Konstruktorem w klasie jest metoda o nazwie __init__. Metoda ta, podobnie jak każda inna metoda czy funkcja, może mieć argumenty domyślne. Pozwala to uzyskać różne wersje konstruktorów.

Plik test.py ma taką zawartość:

class zbiorek:
    def __init__(self,a=0):
        self.store = set()
        self.store.add(a) 
    def show(self):
        print self.store
Wiersz self.store = set() definiuje w klasie zbiorek pole o nazwie store i typie set. Nie można tej definicji skrócić. Poniższa definicja jest poprawna, ale zmienna store jest zmienną lokalną konstruktora. Po wywołaniu metody show wyświetlony zostanie komunikat o nieistniejącej zmiennej store.
class zbiorek:
    def __init__(self,a=0):
        store = set()
        store.add(a) 
    def show(self):
        print store #lub
        print self.store
Przykład użycia klasy zbiorek:
import test
zb = test.zbiorek() # wywołanie konstruktora z wartością domyślną a == 0
zb.show() => set([0])
zb = test.zbiorek(35)
zb.show() => set([35])
zb = test.zbiorek('anakonda')
zb.show() => set(['anakonda'])
Co oznacza zagadkowe słowo self? Wewnątrz konstruktora oznacza wskaźnik (referencję) do właśnie konstruowanego obiektu, wewnątrz innych metod oznacza wskaźnik do obiektu na rzecz którego wywołano metodę. Dzięki temu argumentowi metoda ma dostęp do danych obiektu. Argument ten jest przekazywany do metody bez udziału programisty, w definicji metody musi być pierwszym argumentem. Metoda __init__ jest konstruktorem, ale nie można jej wywołać bezpośrednio w celu skonstruowania obiektu (jest jeden ważny wyjątek związany z dziedziczeniem). Do konstruowania wymagana jest składnia [nazwaModulu.]nazwaKlasy([argumenty]).

Bardziej realistyczny przykład. Plik wektory.py zawiera definicję klasy wektor3d do działań na trójwymiarowych wektorach. Współrzędne wektora pamiętane są jako elementy listy o długości 3.

class wektor3d:
    "klasa do działań na trójwymiarowych wektorach"
    def __init__(self):
        self.wektor=[0,0,0]
        self.wsp = {"x":0,"y":1,"z":2}
    def setVector(self,wektor):
        self.wektor = wektor
    def setCoordinateByNumber(self,which,coordinate):
        self.wektor[which] = coordinate
    def setCoordinateByName(self,which,coordinate):
        self.setCoordinateByNumber(self.wsp[which],coordinate)
    def move(self,wektor):
        for i in range(3):
            self.wektor[i] = self.wektor[i]+wektor[i]
    def add(self,wektor):
        for i in range(3):
            wektor[i] = wektor[i]+self.wektor[i]
        wynik = wektor3d()
        wynik.setVector(wektor)
        return wynik
    def show(self):
        print self.wektor
Klasa zawiera dwie metody (setCoordinateByNumber i setCoordinateByName) do zmiany wartości współrzędnej wektora. Jedna z nich wywołuje drugą, konieczne jest poprzedzenie nazwy drugiej metody słowem self, do drugiej metody nie przekazujemy parametru self.

Metody move i add się różnią. Zmienne w i v są typu wektor3d:

Przykład wykorzystania klasy wektor3d:
import wektory
w = wektory.wektor3d()
w.show() => [0, 0, 0]
w.setVector([1,2,3])
w => <wektory.wektor3d instance at 0x00DAC968>
print w => <wektory.wektor3d instance at 0x00DAC968>
w.show() => [1, 2, 3]
w.move([2,4,6])
w.show() => [3, 6, 9]
w.setCoordinate("x",5)
w.show() => [5, 6, 9]
z = w.add([1,0,0])
z.show() => [6, 6, 9]
w.show() => [5, 6, 9]

Jeżeli zdefiniujemy w klasie metody __getitem__(self,key) oraz __setitem__(self,key,value), to możemy obiekty (instancje) traktować jak słownik. Rozbudowana klasa wektor3d:

class wektor3d:
    ...
    def __getitem__(self,key):
        return self.wektor[key]
    def __setitem__(self,key,value):
        while len(self.wektor)<=key:
            self.wektor.append(0)
        self.wektor[key] = value
Uzyskujemy w ten sposób nie tylko łatwy dostęp do współrzędnych wektora, ale i potencjalnie niebezpieczną możliwość zmiany struktury istniejącego obiektu.
w = wektory.wektor3d()
v = wektory.wektor3d()
w[2] = 4
w.show() => [0, 0, 4]
v[4] = 5
v.show() => [0, 0, 0, 0, 5]
Możliwość zmiany struktury istniejącego obiektu istnieje zawsze (tzn. nie zależy od tego czy zdefiniowane są metody __setitem__ i __getitem__) i zawsze jest niebezpieczna.
w = wektory.wektor3d()
v = wektory.wektor3d()
v.setVector([2,3])
w.move(v) =>
...
IndexError: list index out of range
w.name = "wektor w" # dodanie pola do istniejącego obiektu
del w.wektor # usunięcie pola z istniejącego obiektu
w.show() => 
...
AttributeError: wektor3d instance has no attribute 'wektor'

Warto również zdefiniować w klasie metodę __str__. Jest ona odpowiednikiem metody toString znanej z Javy i C#. Jeżeli metoda ta jest zdefiniowana w klasie K, a zmienna ob jest typu K, to polecenie print ob jest równoważne poleceniu print ob.__str__(). Jeszcze raz rozbudujemy klasę wektor3d:

class wektor3d:
    ...
    def __str__(self):
        s = ""
        for k in self.wsp.keys():
            s+=k+" = "+str(self.wektor[self.wsp.get(k)])+"\n"
        return s
w = wektory.wektor3d()
w.setVector([1,2,3])
w => <wektory.wektor3d instance at 0x00DB1A08>
w.__str__() => 'y = 3\nx = 2\nz = 5\n'
print w =>
y = 3
x = 2
z = 5

Spis treści

Dziedziczenie

Jeżeli chcemy zdefiniować klasę pochodną P dziedziczącą po klasie bazowej B, to początek definicji klasy wygląda tak: class P(B):. W klasie pochodnej możemy zmieniać istniejące atrybuty (właściwości), możemy też dodawać nowe atrybuty.

Klasa bazowa Ssak. Do przechowywania informacji o obiekcie typu Ssak wykorzystywany jest słownik (typ dict).

class Ssak:
    def __init__(self,imie="???"):
        self.dane = {"kind":"Ssak","name":imie,"legs":"???","voice":"???"}   
    def whoAreYou(self):
        print self.dane["kind"]
        print "Name: "+self.dane["name"]
        print "Legs: "+str(self.dane["legs"])
        print "Voice: "+self.dane["voice"]
    def demo(self):
        return "mammal"
Klasa pochodna Wilk nie dodaje nowych atrybutów, zmienia konstruktor, nie zmienia metody whoAreYou, nadpisuje metodę demo.
class Wilk(Ssak):
    def __init__(self,imie="Kazan"):
        self.dane = {"kind":"Wilk","name":imie,"legs":4,"voice":"uuuuuuuuuuu"}
    def demo(self):
        return "wolf"
Klasa pochodna Czlowiek zmienia konstruktor, dodaje dwie nowe metody, zwiększa ilość przechowywanych informacji (nazwisko), nadpisuje metodę demo.
class Czlowiek(Ssak):
    def __init__(self,imie="???"):
        self.dane = {"kind":"Czlowiek","name":imie,"legs":2,"voice":"Witaj"}
        self.dane["nazwisko"]="???"
    def setNazwisko(self,nazwisko):
        self.dane["nazwisko"]=nazwisko
    def getNazwisko(self):
        return self.dane["nazwisko"]
    def demo(self):
        return "man"
Przykład wykorzystania (przy niezbyt realistycznym założenie, że klasy zostały zdefiniowane interaktywnie, nie jest zatem potrzebne polecenie import):
w = Wilk("Bari")
w.whoAreYou() =>
'Wilk'
'Name: Bari'
'Legs: 4'
'Voice: uuuuuuuuuuu'
man = Czlowiek()
print man.getNazwisko() => '???'
man.setNazwisko("Nowak")
print man.getNazwisko() => 'Nowak'
Klasa Kobieta dziedziczy po klasie Czlowiek (dziedziczenie "wielopokoleniowe"), w takim przypadku bywa potrzebne jawne wywołanie metody __init__.
class Kobieta(Czlowiek):
    def __init__(self,imie="Maria"):
        Czlowiek.__init__(self,imie)
        self.dane["kind"] = "Kobieta"
Python pozwala na wielokrotne dziedziczenie, jeżeli klasa P dziedziczy po klasach B1,...,Bn: class P(B1,...,Bn):, to atrybuty szukane są od lewej (klasa B1) do prawej (klas Bn).
class Wilkolak(Wilk,Czlowiek):
    def demo(self):
        return "werewolf"
Wilkolak().whoAreYou() =>
Wilk
Name: Kazan
Legs: 4
Voice: uuuuuuuuuuu
class Wilkolak(Czlowiek,Wilk):
    def demo(self):
        return "werewolf"
Wilkolak().whoAreYou() =>
Czlowiek
Name: ???
Legs: 2
Voice: Witaj
W klasie bazowej Ssak została zdefiniowana metoda demo. Została ona nadpisana w klasach pochodnych Wilk, Czlowiek i Wilkolak. Można na rzecz obiektu z klasy pochodnej wywołać (nadpisaną w tej klasie) metodę z klasy bazowej.
w=Wilkolak()
w.demo() => 'werewolf'
Wilk.demo(w) => 'wolf'
Ssak.demo(w) => 'mammal'

Spis treści

Atrybuty klasy i składowe (elementy) prywatne

Niekiedy potrzebne są takie atrybuty instancji (zmiennych obiektowych), które mają identyczną wartość dla wszystkich instancji. Typowy przykład, to informacja o ilości istniejących instancji. W wielu językach takie atrybuty nazywane są statycznymi (słowo kluczowe static) lub zmiennymi klasy. W poniższym przykładzie zmienna klasy licznik, przechowuje informację ile razy wywołany był konstruktor klasy L.
Definicja atrybutów klasy znajduje się poza konstruktorem.
class L:
    licznik = 0
    def __init__(self):
        self.__class__.licznik+=1
L.licznik => 0
a = L()
L.licznik => 1
a.licznik => 1
for i in range(50):
    L()
a.licznik => 51
Python dopuszcza by w klasie istniały zmienne klasowe i zmienne obiektowe o takiej samej nazwie. Rozróżnienie zmiennej klasowej i zmiennej obiektowej uzyskujemy korzystając ze słowa kluczowego __class__
class L:
    licznik = 0 #zmienna klasowa licznik, konstruktor nie zmienia jej wartości
    def __init__(self):
        self.licznik+=1 #zwiększana jest wartość zmiennej obiektowej licznik
a=L()
a.licznik => 1
a.__class__.licznik => 0
L.licznik => 0

Elementy prywatne

Dostęp do elementów prywatnych jest ograniczony. W Pythonie nie ma słowa kluczowego private, element jest prywatny jeżeli jego nazwa zaczyna się od __ (podwójne podkreślenie) i nie kończy się podwójnym podkreśleniem. Zgodnie z dokumentacją jest to tylko umowa, która może ulec zmianie.
Co może byc prywatne: Plik prywatne.py zawiera definicję klasy z polem prywatnym i metodą prywatną.
class Test:
    def __init__(self):
        self.__prywatne = 2
        self.publiczne = 5
    def pokaz(self):
        self.__pokaz()
    def __pokaz(self):
        print "publiczne "+str(self.publiczne)
        print "prywatne "+str(self.__prywatne)
import prywatne
t = prywatne.Test()
t.__prywatne => 
... 
AttributeError: Test instance has no attribute '__prywatne'
t.publiczne => 5
t.__pokaz() => 
... 
AttributeError: Test instance has no attribute '__pokaz'
t.pokaz() =>
publiczne 5
prywatne 2

Spis treści
Poprzedni wykład     Lista wykładów