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: passKlasa 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.storeWiersz 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.storePrzykł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.wektorKlasa 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:
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] = valueUzyskujemy 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
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: WitajW 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'
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 => 51Python 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
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