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
"""A 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 é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 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ó 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
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 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 __repr__(self):
return str(self.re) + " + " + str(self.im) + "i"
k1 = Komplex(4, 3)
k2 = Komplex(-2, 1)
k3 = k1 + k2
print k1, k2, 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