Nézzük rá az alábbi kódra, melyet egy hallgató írt az órái kredit és óraszám számolására (tutor link):
def osszora(felvett, minden):
ora = 0
for targy in minden:
if targy[0] in felvett:
ora += targy[1]
return ora
def osszkredit(felvett, minden):
kredit = 0
for targy in minden:
if targy[0] in felvett:
kredit += targy[2]
return kredit
# Egy targy formatuma: (nev, oraszam, kredit)
osszes_targy = [
("Info1", 3, 4),
("Info2", 3, 3),
("Kombi1", 4, 4),
("Kombi2", 3, 3)]
felvett_targyak = ["Info1", "Kombi1"]
print osszora(felvett_targyak, osszes_targy)
print osszkredit(felvett_targyak, osszes_targy)
(Megjegyzés: Az objektumorientált programozás, a kivételekkel való hibakezeléshez hasonlóan, olyan technológia, amelynek az előnyei igazán csak nagyobb programok írásánál jönnek elő. Ilyen kis példánál el kell túlozni a problémák nagyságát ahhoz hogy megindokoljuk ezen technikák használatát, de attól még hogy itt esetleg a megoldás bonyolultabbnak tűnik mint a probléma, nagyobb programoknál ugyanez a megoldás már jobb arányt produkál.)
Nézzük mi történik, ha szeretném külön számontartani, hogy az óraszámból mennyi olyan, ahol jelenlétet ellenőriznek. Az lenne a logikus, ha a tuple-ben az össz óraszám mellett lenne. Ezért más kódot is módosítani kell, míg végül valami ilyesmire jutunk (tutor link):
def osszora(felvett, minden):
ora = 0
for targy in minden:
if targy[0] in felvett:
ora += targy[1]
return ora
def osszkredit(felvett, minden):
kredit = 0
for targy in minden:
if targy[0] in felvett:
kredit += targy[3]
return kredit
# Egy targy formatuma: (nev, oraszam, jelenlet, kredit)
osszes_targy = [
("Info1", 3, 2, 4),
("Info2", 3, 2, 3),
("Kombi1", 4, 2, 4),
("Kombi2", 3, 1, 3)]
felvett_targyak = ["Info1", "Kombi1"]
print osszora(felvett_targyak, osszes_targy)
print osszkredit(felvett_targyak, osszes_targy)
A fontos dolog az, hogy megváltoztattam hogy mit tárolok el egy-egy tárgyról, és emiatt meg kellett változtatnom az osszkredit
függvényt, pedig a krediteket így is, úgy is eltároltam. Ebből látható, hogy az ilyen tuple-ös (vagy listás) megoldás nem fenntartható, ha valamit változtatni akarok a tárolási módszeren, akkor annak következtében mindenhol a kódban változtatnom kell, ahol ezeket az adatokat használom.
Egyik alternatíva szótárban tárolni a dolgokat. Ekkor a lista minden eleme egy szótár, ami pontosan ugyanazokat a kulcsokat tartalmazza (tutor link):
def osszora(felvett, minden):
ora = 0
for targy in minden:
if targy["nev"] in felvett:
ora += targy["oraszam"]
return ora
def osszkredit(felvett, minden):
kredit = 0
for targy in minden:
if targy["nev"] in felvett:
kredit += targy["kredit"]
return kredit
osszes_targy = [
{"nev" : "Info1", "oraszam" : 3,
"jelenlet" : 2, "kredit" : 4},
{"nev" : "Info2", "oraszam" : 3,
"jelenlet" : 2, "kredit" : 3},
{"nev" : "Kombi1", "oraszam" : 4,
"jelenlet" : 2, "kredit" : 4},
{"nev" : "Kombi2", "oraszam" : 3,
"jelenlet" : 1, "kredit" : 3}]
felvett_targyak = ["Info1", "Kombi1"]
print osszora(felvett_targyak, osszes_targy)
print osszkredit(felvett_targyak, osszes_targy)
Ez nem egy rossz megoldás, így már be lehet rakni új tulajdonságokat nagyobb probléma nélkül. Azért még van vele egy-két probléma:
"nev"
, "oraszam"
, stb.).ujtargy
nevű függvényt, és mindig azt használjuk ha tantárgyat akarunk létrehozni a kódban (tutor link):
def osszora(felvett, minden):
ora = 0
for targy in minden:
if targy["nev"] in felvett:
ora += targy["oraszam"]
return ora
def osszkredit(felvett, minden):
kredit = 0
for targy in minden:
if targy["nev"] in felvett:
kredit += targy["kredit"]
return kredit
def ujtargy(nev, oraszam, jelenlet, kredit):
return {"nev" : nev, "oraszam" : oraszam,
"jelenlet" : jelenlet, "kredit" : kredit}
osszes_targy = [
ujtargy("Info1", 3, 2, 4),
ujtargy("Info2", 3, 2, 3),
ujtargy("Kombi1", 4, 2, 4),
ujtargy("Kombi2", 3, 1, 3)]
felvett_targyak = ["Info1", "Kombi1"]
print osszora(felvett_targyak, osszes_targy)
print osszkredit(felvett_targyak, osszes_targy)
Ez a dokumentáláson is segít, írhatja azt pl. az osszora
függvény dokumentációja hogy "A minden paraméter tantárgyak adatait tartalmazó szótárakat tartalmaz, melyek az ujtargy
függvénnyel lettek létrehozva. Azt is megoldja, hogy ha a kódban elfelejtjük mindenhol betenni a plusz paramétereket, akkor már egyből az objektum létrehozásakor szólni fog a python hogy van hiányzó paraméter, nem csak később, használat közben derül ki esetleg a hiba.
Így már elég közel járunk egy tényleges osztály koncepcióhoz.
Az osztályra gondolhatunk úgy, mint egy típusra (pl lista), míg az objektumra úgy miny egy ilyen típusú valami.
Hozzunk létre egy Komplex
osztályt. (Szokás az osztályok neveit mind nagybetűsnek venni, hogy könnyebben megkülönböztethető legyen. A python dokumentáció is ajánlja ezt mint egy lehetőséget, bár a python beépített osztályok nem követik ezt a módszert. Az "object" helyére kerülhetne más is elvileg, de ezt csak később látjuk majd.)
class Komplex(object):
pass
Ezzel már létezik a Komplex
osztály. Nem tud még semmit, de létrehozhatunk egy ilyen típusú objektumot:
k = Komplex()
Sőt, akár ennek adhatunk adattagokat is (valós és képzetes rész):
k.re = 5
k.im = 2
A . (pont) operátorral érhetjük el egy objektum adattagjait és metódusait. (Ilyen volt pl a listáknál az append
.)
Bár lehetséges egy objektum létrehozása után adattagokat adni hozzá, szerencsésebb lenne, ha a létrehozásakor adtuk volna meg neki ezeket az értékeket. Ehhez létezik az osztálynak konstruktora:
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
k = Komplex(4, 3)
print k.re
print k.im
Egy osztály konstruktora a speciális nevű \__init__
metódus (osztályon belül definiált függvény = metódus).
Ennek a self
paramétere az épp létrehozandó objektumra mutat, így állítjuk be a létrehozandó objektum adattagjait.
Nézzük meg most a korábbi példát osztályokkal (tutor link):
def osszora(felvett, minden):
ora = 0
for targy in minden:
if targy.nev in felvett:
ora += targy.oraszam
return ora
def osszkredit(felvett, minden):
kredit = 0
for targy in minden:
if targy.nev in felvett:
kredit += targy.kredit
return kredit
class Targy(object):
def __init__(self, nev, oraszam, jelenlet, kredit):
self.nev = nev
self.oraszam = oraszam
self.jelenlet = jelenlet
self.kredit = kredit
osszes_targy = [
Targy("Info1", 3, 2, 4),
Targy("Info2", 3, 2, 3),
Targy("Kombi1", 4, 2, 4),
Targy("Kombi2", 3, 1, 3)]
felvett_targyak = ["Info1", "Kombi1"]
print osszora(felvett_targyak, osszes_targy)
print osszkredit(felvett_targyak, osszes_targy)
Térjünk most vissza a komplexes példára. Milyen jó lenne, ha tudnánk összeadni komplexeket. Írjunk hát erre egy függvényt:
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
def komplex_osszeg(k1, k2):
uj_re = k1.re + k2.re
uj_im = k1.im + k2.im
return Komplex(uj_re, uj_im)
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = komplex_osszeg(k1, k2)
print k3.re
print k3.im
Nade mit kerese ez a függvény csak úgy a kódunkban, mikor érezhető, hogy valójában a Komplex
osztályhoz tartozik:
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
def komplex_osszeg(k1, k2):
uj_re = k1.re + k2.re
uj_im = k1.im + k2.im
return Komplex(uj_re, uj_im)
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = Komplex.komplex_osszeg(k1, k2)
print k3.re
print k3.im
Figyeljük meg, hogy a függvényt Komplex.komplex_osszeg
-két értük el. Nem túl szép ez így, de lehetne szebben:
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
def osszeg(self, k2):
uj_re = self.re + k2.re
uj_im = self.im + k2.im
return Komplex(uj_re, uj_im)
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = k1.osszeg(k2)
print k3.re
print k3.im
Tehát úgy tudunk metódust gyártani, ha egy osztályon belüli függvény első paraméterét self
-re állítjuk. Ekkor a self
arra az objektumra fog utalni, amelyen meghívtuk a függvényt, jelen esetben k1
-re.
Az összes többi paraméter a metódus paramétere lesz, ebben az esetben k2
.
Ez így már tűrhetően olvasható, de az az igazság, hogy még ennél is szebbé tehetjük:
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
def __add__(self, k2):
uj_re = self.re + k2.re
uj_im = self.im + k2.im
return Komplex(uj_re, uj_im)
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = k1 + k2
print k3.re
print k3.im
Az \__add__
is egy speciális metódus, mely a +
operátor működését definiálja. Első paramétere self
a bal oldali objektumre (ebben az esetben k1
-re) utal, míg második paramétere a jobb oldalira (ebben az esetben k2
-re).
Hasonlóan van ilyen a többi operátorra: \__sub\__
, \__mul\__
, \__div\__
Egy utolsó szépséghiba a kódunkban még a kiírás, hisz ideális esetben ezzel szépen írná ki a számot:
print k3
Erre is van megoldás:
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
def __add__(self, k2):
uj_re = self.re + k2.re
uj_im = self.im + k2.im
return Komplex(uj_re, uj_im)
def __repr__(self):
s = ""
s += str(self.re)
s += " + "
s += str(self.im)
s += "i"
return s
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = k1 + k2
print k3
A \__repr__
speciális metódusnak egy stringet kell visszaadnia és amikor meghívunk egy ilyen típusú objektumon egy kiírást, akkor ezt a metódust fogja használni az objektum reprezentálására.
Ez természetesen még nem tökéletes, hisz például negatív képzetes résznél csúnya, de lehetne még tovább javítani:
print Komplex(-2, -1)