[[:oktatas:programozás:python|< Python]]
====== Python Objektum Orientált Programozás ======
* **Szerző:** Sallai András
* Copyright (c) 2011, Sallai András
* Szerkesztve: 2011, 2020, 2022, 2024
* 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.
===== Típusok =====
A 3.5-ös Python verziótól használható:
class Employee:
name: str
city: str
salary: int
def __init__(self, name, city, salary):
self.name = name
self.city = city
self.salary = salary
mari = Employee('Mari', 'Pécs', 395)
print(mari.name, "fizetése:", mari.salary, "LTC")
class Employee:
name: str
city: str
salary: int
def __init__(self, name, city, salary):
self.name = name
self.city = city
self.salary = salary
def __str__(self):
return f"{self.name} ({self.city} Fizetés: {self.salary} LTC)"
mari = Employee('Mari', 'Pécs', 395)
print(mari)
# Kimenet: Mari (Pécs Fizetés: 395 LTC)
===== Külső linkek =====
* https://www.geeksforgeeks.org/python-method-overloading/ (2020)