[[:oktatas:programozás:python|< Python]] ====== Python Objektum Orientált Programozás ====== * **Szerző:** Sallai András * Copyright (c) Sallai András, 2011, 2020, 2022 * Licenc: [[https://creativecommons.org/licenses/by-sa/4.0/|CC Attribution-Share Alike 4.0 International]] * Web: https://szit.hu ===== Osztály ===== Az osztályban első sorban az összetartozó tulajdonságokat tároljuk. A következő példában tároljuk el egy dolgozó, nevét, a települését, és életkorát. A feladat a következő UML diagrammal ábrázolható. {{:oktatas:programozas:python:janos_dolgozo_objektum.png}} Az UML diagramban az osztályt egy téglalappal ábrázoljuk, amelyet vízszintesen 3 részre osztunk. A legfelső részbe kerül az osztály neve, középen a tulajdonságok, és alul a viselkedések. Jelenleg egyetlen viselkedés sincs. Az osztályban tárol tulajdonságokat adattagoknak is szokás hívni. A Python nyelvben az osztályt a class kulcsszóval vezetjük be. A class után az osztály neve következik, amit egy kettőspont (:) követ. Üres osztályra példa: class Dolgozo: pass A pass azt jelenti, hogy nem valósítottuk meg az osztály, majd később fogjuk megtenni. A Dolgozo osztályra példa: class Dolgozo: nev = 'Névtelen' telepules = 'Ismertelen' kor = 0 janos = Dolgozo() janos.nev = 'Nagy János' janos.telepules = 'Szolnok' janos.kor = 35 print('{}\n{}\n{}'.format( janos.nev, janos.telepules, janos.kor )) A fenti példában az osztály neve után : (kettőspont) áll. Zárójelek is szerepelhetenk a kettőspont előtt: class Dolgozo(): pass A zárójeleknek a később tárgyalt öröklésnél lesz szerepe, de így öröklés nélkül is kitehetők. ==== Mutató az osztályra ==== Tévesen szokás példányosításnak megadni: class Dolgozo: pass pali = Dolgozo Vegyük észre a zárójelek hiányát. Ilyen esetben **nem történik konstruktor hívás, és nem jön létre új példány**! Csak egy újabb **mutató jön létre** a Dolgozo osztályra. Ha most létrehozok a pali számára egy nev adattagot, az létrejön a Dolgozo osztály számára is: class Dolgozo: pass pali = Dolgozo pali.nev = 'Nagy Pál' print(Dolgozo.nev) A Dolgozo néven keresztül megkapjuk a nev mező tartalmát. Ha a pali egy objektum példány lenne, ez nem történhetne meg. De nézzük meg az "is" ((Az is operátor megmondja, hogy két különböző nevű szimbólum azonos objektumra mutat vagy nem.)) operátorral: class Dolgozo: pass pali = Dolgozo print(Dolgozo is pali) Az eredmény True lesz, vagyis annyit csináltunk, hogy az Dolgozo osztály két néven érhető el. De akkor logikusabb lenne egy ilyen: class Dolgozo: pass Munkas = Dolgozo De megnézhetjük az objektumok azonosítóját is, az id() ((A Python nyelvben minden objektumnak van egy azonosítója. Az id() függvény segítségével lekérdezhető ez az azonosító.)) függvénnyel: class Dolgozo: pass pali = Dolgozo print(id(Dolgozo)) print(id(pali)) Eredményül ugyanazt kapjuk, a címük megegyezik. Ebből következik, hogy példány így nem hozható létre. Ha statikusan használjuk a Dolgozo osztályt, akkor használhatjuk zárójelek nélkül, az osztály nevét, de akkor nem szoktunk mutatót létrehozni a már meglévő osztályra. A következő példa helyes, statikus tag használatára: calss Dolgozo: pass # Beállíthatók egy statikus adattagot: Dolgozo.minimumFizetes = 348 Vegyük észre, hogy nem használtunk zárójelet, a Dolgozo osztály neve után. Ha pedig példányt akarunk létrehozni, akkor azt helyesen így tehetjük meg: class Dolgozo: pass pali = Dolgozo() A pali objektum, így egy példánya a Dolgozo osztálynak. ===== Metódusok ===== A metódusok az osztályon belül viselkedést írnak le. Ha egy dolgozót szimulálunk, akkor ilyen viselkedés lehetnek a következők: * dolgozik * utazik * beszél * pihen Metódusokkal írjuk le az adattagokat beállító és lekérdező részeket is. A metódusok olyanok mint a függvények, csak osztályon belül. Az UML ábrán a nev tag beállító és lekérdező metódust látjuk: {{:oktatas:programozas:python:dolgozo_metodus.png|}} class Dolgozo: Nev = "Névtelen" def beallitNev(self, atvettNev): self.Nev = atvettNev def lekerNev(self): return self.Nev Joska = Dolgozo() Joska.beallitNev("Nagy József") print Joska.lekerNev() Az osztály metódusait a def kulcsszó vezeti be. Minden metódus első paramétere egy speciális paraméter, mivel ez mindig automatikusan értéket kap, mindig a létrejött objektumra mutat automatikusan. Elnevezése tetszőleges, de szokásosan **self**. ===== Konstruktor ===== A konstruktor az osztály adattagjainak előkészítésére való. Egy speciális metódus, amely egy objektum létrehozásakor kerül meghívásra. A Python nyelvben a konstruktort egy __init__() nevű metódus jelképezi. Az első paraméter itt is a self. class Dolgozo: def __init__(self): self.nev = 'Névtelen' self.Kor = 0 self.nem = 'ismeretlen' janos = Dolgozo() janos.nev = 'Nagy János' janos.kor = 35 janos.nem = 'férfi' print(janos.nev) print(janos.kor) print(janos.nem) A konstruktornak paraméterként átadhatók az adattagok kezdőértékei. class Dolgozo: def __init__ (self, nev, kor, nem): self.Nev = nev self.Kor = kor self.Nem = nem def lekerKor(self): return self.Kor Joska = Dolgozo("Nagy József", 25, "f") print Joska.Kor ==== Több alakú konstruktor ==== class Dolgozo: def __init__ (self, nev='névtelen', kor=0, nem='semleges'): self.nev = nev self.kor = kor self.nem = nem joska = Dolgozo("Nagy József", 25, "f") janos = Dolgozo() lajos = Dolgozo(kor=32) # egyéb példák: piri = Dolgozo() dani = Dolgozo(kor=32, nev="Nagy József) ===== Öröklés ===== Az elkészített osztályokat később felhasználhatjuk újabb osztályok létrehozására. Az eredeti osztály ilyenkor a szülő vagy alaposztály lesz. Az osztály örökli az adattagokat és a metódusokat, kivéve a konstruktort. Az öröklés esetén a szülőosztályt az új osztály neve után adjuk meg. A példánkban az szülőosztály a Dolgozo, amit szeretnénk a Mernok osztályban örökíteni. Ezért így kezdem a Mernok osztály leírását: class Mernok(Dolgozo): Egyszerű példa: class Dolgozo(): def beszel(self): print('Szia') class Mernok(Dolgozo): def pihen(self): print('Alszom...') pali = Mernok() pali.beszel() Összetettebb példa: class Dolgozo: def __init__(self): self.Nev="Névtelen" def lekerNev(self): return self.Nev def beallitNev(self, atvettNev): self.Nev = atvettNev class Mernok(Dolgozo): def __init__(self): self.Diploma = "Ismeretlen" def lekerDiploma(self): return self.Diploma # Mérnök példány létrehozása tibi = Mernok() # Az új metódus használata: print(tibi.lekerDiploma()) # Az ősosztály metódusának hívása tibi.beallitNev("Nagy Tibor") # Az ősosztály metódusának hívása print(tibi.lekerNev()) A példában a Mernok osztály örökli a Dolgozo osztály adattagjait és metódusait. ===== A super() metódus ===== A super() metódus lehetővé teszi, hogy a szuper osztály eredeti metódusait meghívjuk. Az ősosztály konstruktorának hívása: class Dolgozo: def __init__(self): self.Nev="Névtelen" def lekerNev(self): return self.Nev def beallitNev(self, atvettNev): self.Nev = atvettNev class Mernok(Dolgozo): def __init__(self): super().__init__() self.Diploma = "Ismeretlen" def lekerDiploma(self): return self.Diploma # Mérnök példány létrehozása tibi = Mernok() # Az ősosztályban előkészített nev lekérdezése print(tibi.lekerNev()) Ha nem hívjuk meg az ősosztály konstruktorát, akkor nem működik a lekerNev() metódus. A super() metóduson keresztül, más metódusok hívhatók, a következőben erre látunk egy példát. Vegyünk egy Dolgozo osztályt, amely a munkát úgy szemlélteti, hogy a képernyőre írja az 'ások' szót. A dolgozó osztályból készítünk egy Mernok osztályt. A munka() metódust, most felülírjuk, ezzel mert a mérnök mér. Ezt úgy látjuk, hogy kiírja a program, hogy 'mérek'. Ha mégis szükség lenne arra, hogy a Mernok osztályból meg tudjuk hívni a Dolgozo osztály munka() metódusát, akkor azt a super() metóduson keresztül tudjuk megtenni: class Dolgozo: def munka(self): print('ások') class Mernok(Dolgozo): def munka(self): print('mérek') def asas(self): super().munka() joska = Mernok() joska.asas() ===== Statikus adattagok ===== Statikus adattag: class Valami(): EGY=0 KETTO=1 print(Valami.EGY) A statikus tagok elérhető példányon keresztül is: valami = Valami() print(valami.EGY) Más értéket is adhatunk: valami = Valami() valami.EGY = 3 Valami.EGY = 4 ===== Példány, osztály és statikus metódus ===== Egy osztályban háromféle metódust hozhatunk létre: class Mycalss: def my_method(self): return 'példánymetódus hívása', self @classmethod def my_classmethod(cls): return 'osztálymetódus hívása', cls @staticmethod def my_staticmethod(): return 'statikus metódushívás' ==== Példány metódus ==== A példánymetódus, egy általános példánymetódus. A példánymetódusok egy önmagukra mutató paramétert igényelnek, de megadhatók más paraméterek is. A self paraméteren keresztül a példánymetódusok szabadon elérhetik az adattagokat (attribútumok), és más metódusokat, ugyanazon az objektumon. Így könnyen módosítható az objektum állapota. ==== Osztálymetódus ==== Az osztálymetódust a **classmethod dekorátorral** kell megjelölni. A self paraméter helyet az osztálymetódusok egy cls paramétert használnak, amely az osztályra mutat és nem az objektumpéldányra, amikor meghívásra kerül. Mivel az osztálymetódus csak a cls argumentumhoz fér hozzá, ezért **nem módosíthatja** az **objektum állapotát**. Az osztálymetódusok, azonban továbbra is **módosíthatják** az **osztály állapotát**, amely az osztály összes példányára érvényes. ==== Statikusmetódus ==== A statikusmetódusokat a staticmethod dekorátorral kell megjelölni. Az ilyen metódus nem veszi figyelembe sem a self, sem a cls paramétert, de lehet tetszőleges számú más paramétere. A statikusmetódus **nem módosíthatja sem az objektum, sem az osztály állapotát**. ==== Statikus metódus példa ==== Saját matematikai osztály: class Math: @staticmethod def abs(x): if x >= 0: return x else: return x * -1 @staticmethod def pow(a, b): if b == 0: return 1 if b == 1: return a tmp = 1 for i in range(b): tmp *= a return tmp print(Math.abs(-9)) print(Math.pow(8, 2)) ===== Kivételkezelés ===== ==== Finally nélkül ==== def tryReadfile(): fp = open('adat.txt', 'r', encoding='utf-8') lista = fp.read().splitlines() fp.close() return lista def readfile(): try: return tryReadfile() except: print('Hiba! A fájl olvasása sikertelen!') print(readfile()) ==== Finally-val ==== def tryReadfile(): fp = open('adat.txt', 'r', encoding='utf-8') lista = fp.read().splitlines() datas = [] datas.append(lista) datas.append(fp) return datas def readfile(): try: datas = tryReadfile() lista = datas[0] fp = datas[1] return lista except: print('Hiba! A fájl olvasása sikertelen!') finally: fp.close() print(readfile()) ===== Konstruktor túlterhelése ===== A Python nyelven a konstruktorból csak egyet írhatok, nincs valódi túlterhelés. Ha szeretnénk többféle paraméterrel használni, akkor a paramétereknek kezdő értéket kell adni. Így a paraméterek megadása nem kötelező. class Dolgozo: def __init__(self, atvettNev='Névtelen', atvettTelepules='ismeretlen'): self.nev = atvettNev self.telepules = atvettTelepules janos = Dolgozo() print(janos.nev) mari = Dolgozo('Pere Mária', 'Szeged') print(mari.nev) ==== Konstruktor túlterhelése gyártómetódussal ==== A konstruktorok túlterhelésére a paraméterek kezdőértékénél tisztább megoldást kínál a gyártómetódus használata. class Dolgozo: def __init__(self, atvettNev: str, atvettTelepules: str): self.nev = atvettNev self.telepules = atvettTelepules @classmethod def gen(cls): return cls(atvettNev='Névtelen', atvettTelepules='ismeretlen') janos = Dolgozo.gen() print(janos.nev) mari = Dolgozo('Pere Mária', 8) print(mari.nev) Felhasznált webhely: * https://medium.com/@shamir.stav_83310/overloading-constructors-in-python-17e42b7430b6 (2020) ===== Metódusok túlterhelése ===== Ha szeretnék olyan metódust írni, amit többféle képen is lehet paraméterezni adhatunk a paramétereknek kezdőértéket. class Dolgozo: def __init__(self, atvettNev, atvettTelepules): self.nev = atvettNev self.telepules = atvettTelepules def beallit(self, atvettNev='Névtelen', atvettTelepules='ismeretlen'): self.nev = atvettNev self.telepules = atvettTelepules mari = Dolgozo('Pere Mária', 'Szeged') print(mari.nev, ' ', mari.telepules) mari.beallit('Park Mária') print(mari.nev, ' ', mari.telepules) mari.beallit('Park Mária', 'Szolnok') print(mari.nev, ' ', mari.telepules) ==== A multipledispatch modul ==== Másik megoldás lehet a multipledispatch modul használata. A használathoz a modult telepíteni szükséges: pip3 install multipledispatch from multipledispatch import dispatch class Dolgozo: def __init__(self, atvettNev, atvettTelepules): self.nev = atvettNev self.telepules = atvettTelepules @dispatch(str) def beallit(self, atvettNev): self.nev = atvettNev @dispatch(str, str) def beallit(self, atvettNev, atvettTelepules): self.nev = atvettNev self.telepules = atvettTelepules mari = Dolgozo('Pere Mária', 'Szeged') print(mari.nev, ' ', mari.telepules) mari.beallit('Park Mária') print(mari.nev, ' ', mari.telepules) mari.beallit('Park Mária', 'Szolnok') print(mari.nev, ' ', mari.telepules) ===== Interfész ===== A Python nyelv nem támogatja. De van néhány modul, amivel hasonló megvalósítható. * interface * abc ==== interface ==== from interface import Interface, implements class iPelda(Interface): def valami(self): pass class Mas(implements(iPelda)) def valami(self): print('Működik') ===== Láthatóság ===== Alapértelmezetten minden adattag és metódus látható. A következő láthatóságok állíthatók be: * public * protected * private A public vagyis nyilvánosan hozzáférésű változók osztályon belül és osztályon kívül is elérhetők. A protected elérésű változók elérhetők még az ősosztályokban, de azon kívül sehol. A privát elérésű változók csak az osztályon belül érhetők el. class Valami: def __init__(self): # public: self.egy = 'első' # protected: self._ketto = 'második' # private self.__harom = 'harmadik' Láthatjuk, hogy a védelmet az adattag elé írt alsó-vonalak adják. ===== Gyakori hiba ===== Ez nem példány: lajos = Dolgozo Helyesen: lajos = Dolgozo() Nézzük meg a következő program kimenetét: class Dolgozo: pass lajos = Dolgozo print(lajos is Dolgozo) Az eredmény True, vagyis a lajos csak egy mutató a Dolgozo osztályra. ===== Külső linkek ===== * https://www.geeksforgeeks.org/python-method-overloading/ (2020)