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 e technika használatát. Kb. 500 soros programig könnyű elboldogulni az eddig tanult eszközökkel, de a fölött az OOP elemei látványosan könnyebbé, gyorsabbá teszik a programírást.)
Nézzük mi történik, ha szeretném külön számontartani, hogy az óraszámból hányban ellenőriznek jelenlétet. Az lenne a logikus, ha a tuple-ben az össz óraszám mellett lenne a jelenlét. Így módosul a kód (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] #<<< itt kell modositani
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""".
Ez azt is megoldja, hogy ha a kódban elfelejtjük mindenhol betenni a plusz paramétereket, akkor már az objektum létrehozásakor szól 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 mint egy ilyen típusú példányára.
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 e módszert. Az object
helye maradhat üresen is e példában, később látjuk majd, mit írhatunk oda.)
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
print k.re
A . (pont) operátorral érhetjük el egy objektum adattagjait és tagfüggvényeit (= metódusait = osztályon belül definiált függvényeit). 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, k.im
Egy osztály konstruktora a speciális nevű \__init__
különleges metódus (speciális metódus). Ezek előre adott nevű metódusok, melyek mindig két aláhúzás-karakterrel kezdődnek és azzal végződnek (így a programozó kódja ezekkel nem akadhat össze, mert ilyen nevű metódus csak a megadottak közül választható, amelynek pontosan definiálva van a funkciója).
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, k3.im
Mit keres ez a függvény csak úgy a kódunkban, hisz é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, k3.im
Figyeljük meg, hogy a függvényt Komplex.komplex_osszeg
-két értük el, hisz így tudatni kell a pythonnal, hogy mely osztályhoz tartozó függvényt hívjuk. Nem túl szép, lehetne kicsit 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, 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. Innen tudja a python, hogy mely osztályhoz tartozó osszeg
függvényt kell meghívnia (lehet más osztálynak ilyen nevű függvénye).
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, k3.im
Persze az \__add__
metódus közvetlen meghívásával is lehetne:
k4 = k1.__add__(k2)
print k4
print k4.re, k4.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 objektumra (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 hiány, a kiírás:
print k3
Erre is van különleges metódus:
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):
return str(self.re) + " + " + str(self.im) + "i"
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = k1 + k2
print k3
Komplex(3, 2)
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.
Még ez sem tökéletes:
print Komplex(0, 0) # 0
print Komplex(3, 0) # 3
print Komplex(-2, 1) # -2 + i
print Komplex(-2, -1) # -2 - i