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 (Object Oriented Programming) elemei látványosan könnyebbé, gyorsabbá teszik a programírást.
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)
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 több helyen 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
minden
paraméter tantárgyak adatait tartalmazó szótárak listája, 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.
Például az 5
szám példánya az int
típusnak.
Hozzunk létre egy Targy
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.
class Targy(object):
pass
Ezzel már létezik a Targy
osztály. Nem tud még semmit, de létrehozhatunk egy ilyen típusú objektumot az osztály nevével utána zárójellel:
t = Targy()
Sőt, akár ennek adhatunk adattagokat is (valós és képzetes rész):
t.nev = "Info2"
t.oraszam = 3
print t.oraszam
A . (pont) operátorral érhetjük el egy objektum adattagjait (vagy tagváltozóit) és tagfüggvényeit (metódusait osztályon belül definiált függvényeit). Ilyen volt pl a listáknál az append
.
Az adattagok és metódusok gyűjtőneve az attribútum.
Bár lehetséges egy objektum létrehozása után adattagokat adni hozzá, szerencsésebb lenne, ha már a létrehozásakor adtuk volna meg neki ezeket az értékeket. Ehhez létezik az osztálynak konstruktora:
class Targy(object):
def __init__(self, nev, oraszam, jelenlet, kredit):
self.nev = nev
self.oraszam = oraszam
self.jelenlet = jelenlet
self.kredit = kredit
Egy osztály konstruktora a speciális nevű __init__
különleges metódus (speciális metódus).
Akkor fejti ki hatását, ha létrehozok egy példányt egy osztályból.
Ezt az osztály neve után zárójel és esetleges paraméterek felsorolásával tehetjük meg.
t = Targy( x, y, ... )
Ennek a self
paramétere az épp létrehozandó objektum, í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
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)
Hozzunk létre egy Komplex
osztályt komplex számok kezelésére.
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
k = Komplex(4, 3)
print k.re, k.im
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
Ez a függvény valójában szorosan tartozik a Komplex
osztályhoz, jobb ha oda is tesszük.
class Komplex(object):
def __init__(self, real, imaginary):
self.re = real
self.im = imaginary
def 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
Még jobb ha nem kell kiírni az osztály nevét, csak a két megfelelő dolgot összeadni. Ez a metódus.
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 írni, 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 (ami a pont bal oldalán áll), 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 (pont utáni) 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.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).
Speciális metódus lehet más is, például: __sub__
, __mul__
, _div__
. De ilyen volt az __init__
is.
Ezek azért speciálisak mert nem csak a nevükkel, hanem operátor által is meg lehet hívni. Ezek a python nyelv által definiált véges névkészletből valóak. Mindig két aláhúzás-karakterrel kezdődnek és azzal végződnek.
Ez sajnos nem jó:
print k3
Van egy speciális metódus, ami a print
meghívásakor illetve string-é alakításkor fejti ki hatását.
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 __str__(self):
return str(self.re) + " + " + str(self.im) + "i"
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = k1 + k2
print k1
print k2
print k3
str(Komplex(3, 2))
A __str__
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ódus fog lefutni.
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