1. fejezet
Osztály és objektum
Eddig az adatokat és a velük dolgozó függvényeket külön kezeltük: egy diák nevét, jegyeit külön változókban vagy egy szótárban, az átlagszámítást egy külön függvényben. Az objektumorientált programozás (OOP) összefogja őket: egy egységbe kerül az adat és az, amit vele csinálni lehet.
Az osztály egy tervrajz: leírja, milyen adatai (attribútumai) és milyen műveletei (metódusai) lesznek az ilyen típusú dolgoknak. Az objektum (példány) egy konkrét dolog, amely a tervrajz alapján készült, saját adatokkal. Egy osztályból tetszőleges számú objektum készülhet.
Valójában eddig is objektumokkal dolgoztál: az "alma" a str osztály egy példánya, ezért van upper() metódusa; a [3, 1, 2] a list osztályé, ezért van append()-je. Most saját osztályt írunk.
szo = "alma" lista = [3, 1, 2] print(type(szo), szo.upper()) print(type(lista)) lista.append(4) print(lista)
2. fejezet
Az első osztály: class, __init__, self
class Kutya:
def __init__(self, nev, kor):
self.nev = nev
self.kor = kor
def bemutatkozik(self):
return f"{self.nev} vagyok, {self.kor} éves."
bodri = Kutya("Bodri", 3)
morzsa = Kutya("Morzsa", 7)
print(bodri.nev)
print(morzsa.bemutatkozik())
bodri.kor = bodri.kor + 1
print(bodri.bemutatkozik())
class Kutya:osztálydefiníció; a név PascalCase__init__a konstruktor (pontosabban inicializáló): a példány létrehozásakor fut le, és beállítja az adataitselfaz éppen kezelt objektum – mindig az első paraméterself.nev = nevattribútum létrehozása a példánybanKutya("Bodri", 3)példányosítás: új objektum
Kövesd Lépésenként. A Kutya("Bodri", 3) hívásakor a Python létrehoz egy üres objektumot, majd meghívja rá az __init__-et: a self ez az új objektum, a nev és a kor a kapott argumentumok. A self.nev = nev sor az objektumba „írja” az adatot. A Memóriában két külön Kutya objektum doboz jelenik meg, saját attribútumokkal, és a bodri meg a morzsa név nyíllal mutat rájuk.
A self-et a hívásnál nem adjuk át – a Python teszi meg helyettünk: a bodri.bemutatkozik() valójában Kutya.bemutatkozik(bodri). Ha a metódus fejlécéből lemarad a self, a hívás TypeError-t ad („takes 0 positional arguments but 1 was given”). Az __init__ két oldalán két-két aláhúzás áll.
3. fejezet
Metódusok és az objektum állapota
Az objektum attribútumainak pillanatnyi értéke az objektum állapota. A metódusok olvassák és módosítják ezt az állapotot – és vigyáznak rá, hogy érvényes maradjon. Egy bankszámla egyenlege például ne lehessen negatív, és negatív összeget se lehessen befizetni:
class BankSzamla:
def __init__(self, tulajdonos, egyenleg=0):
self.tulajdonos = tulajdonos
self.egyenleg = egyenleg
def befizet(self, osszeg):
if osszeg <= 0:
return False
self.egyenleg += osszeg
return True
def kivesz(self, osszeg):
if osszeg <= 0 or osszeg > self.egyenleg:
return False
self.egyenleg -= osszeg
return True
def atutal(self, cel, osszeg):
if self.kivesz(osszeg):
cel.befizet(osszeg)
return True
return False
anna = BankSzamla("Anna", 10000)
bela = BankSzamla("Béla")
print(anna.kivesz(25000))
print(anna.atutal(bela, 3000))
print(anna.befizet(-500))
print(anna.tulajdonos, anna.egyenleg, "|", bela.tulajdonos, bela.egyenleg)
Az atutal metódus egy másik számlaobjektumot kap paraméterként, és annak a metódusát hívja. A lépésenkénti nézetben a self és a cel két különböző objektumra mutat.
Mit ír ki?
class Szamlalo:
def __init__(self):
self.ertek = 0
def novel(self):
self.ertek += 1
a = Szamlalo()
b = a
c = Szamlalo()
a.novel()
b.novel()
c.novel()
print(a.ertek, b.ertek, c.ertek)
2 2 11 1 12 1 1
A b = a nem hoz létre új objektumot: az a és a b ugyanarra az egy számlálóra mutat (mint a listáknál a 4. modulban). Ezt kétszer növeltük. A c egy második, független objektum.
4. fejezet
Szöveges megjelenítés és összehasonlítás: speciális metódusok
Ha egy saját objektumot kiírunk, alapból valami ilyesmit kapunk: <__main__.Konyv object at 0x7f…>. A speciális metódusokkal (dupla aláhúzásos, angolul „dunder” nevekkel) megmondhatjuk, hogyan viselkedjen az objektumunk a Python beépített műveleteiben:
| Metódus | Mikor hívódik meg? |
|---|---|
__str__(self) | print(obj), str(obj), f-string – felhasználóbarát szöveg |
__repr__(self) | repr(obj), illetve ha az objektum listában vagy szótárban jelenik meg – programozói leírás |
__eq__(self, masik) | a == b |
__lt__(self, masik) | a < b – és ezzel a sorted(), min(), max() is működik |
__add__(self, masik) | a + b |
__len__(self) | len(obj) |
class Konyv:
def __init__(self, cim, szerzo, oldal):
self.cim = cim
self.szerzo = szerzo
self.oldal = oldal
def __str__(self):
return f"{self.szerzo}: {self.cim} ({self.oldal} oldal)"
def __repr__(self):
return f"Konyv({self.cim!r}, {self.szerzo!r}, {self.oldal})"
def __lt__(self, masik):
return self.oldal < masik.oldal
polc = [
Konyv("Egri csillagok", "Gárdonyi Géza", 560),
Konyv("Pál utcai fiúk", "Molnár Ferenc", 240),
Konyv("Légy jó mindhalálig", "Móricz Zsigmond", 330),
]
print(polc[0])
print(polc)
for k in sorted(polc):
print(k)
print("A legrövidebb:", min(polc))
A !r az f-stringben a repr() alakot kéri – így a szövegek idézőjelben jelennek meg, ahogy a kódban írnánk őket.
Pontok összeadása: __add__ és __eq__
class Vektor:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, masik):
return Vektor(self.x + masik.x, self.y + masik.y)
def __eq__(self, masik):
return self.x == masik.x and self.y == masik.y
def __str__(self):
return f"({self.x}, {self.y})"
a = Vektor(1, 2)
b = Vektor(3, 4)
c = a + b
print(c)
print(c == Vektor(4, 6), a == b)
Az a + b a háttérben a.__add__(b) hívás, és egy új Vektor objektumot ad vissza – az a és a b nem változik. __eq__ nélkül a == azt vizsgálná, hogy ugyanaz az objektum-e a kettő, nem azt, hogy egyenlő-e a tartalmuk. (Jó tudni: ha egy osztály megadja az __eq__-t, a példányai alapból nem tehetők halmazba és nem lehetnek szótárkulcsok – ehhez egy __hash__ metódus is kellene.)
5. fejezet
Osztályváltozó és példányváltozó
A self.valami attribútumok a példányhoz tartoznak: minden objektumnak saját van belőlük. Az osztály törzsében (a metódusokon kívül) létrehozott változó viszont az osztályhoz tartozik, és minden példány közösen látja. Jó például állandókhoz és az összes példányra vonatkozó adatokhoz (hány objektum készült).
class Termek:
AFA = 0.27
darab = 0
def __init__(self, nev, netto):
self.nev = nev
self.netto = netto
Termek.darab += 1
def brutto(self):
return round(self.netto * (1 + Termek.AFA))
t1 = Termek("füzet", 400)
t2 = Termek("toll", 250)
print(t1.brutto(), t2.brutto())
print("Létrehozott termékek:", Termek.darab)
print(t1.AFA)
Csapda: ha egy osztályváltozónak a self-en keresztül adsz értéket (self.darab += 1), nem a közös változó nő, hanem a példányban jön létre egy új, azonos nevű attribútum, amely eltakarja az osztályét. Az osztályváltozót ezért az osztály nevén keresztül módosítsd: Termek.darab += 1.
Mit ír ki?
class Gomb:
kattintas = 0
def katt(self):
self.kattintas += 1
a = Gomb()
b = Gomb()
a.katt()
a.katt()
b.katt()
print(a.kattintas, b.kattintas, Gomb.kattintas)
3 3 32 1 32 1 0
A self.kattintas += 1 a példányba ír: az első híváskor kiolvassa az osztályváltozót (0), és a példányban létrehoz egy saját kattintas attribútumot (1). Így az a-nak 2, a b-nek 1 saját kattintása lesz, az osztályváltozó pedig 0 marad. Ha a közös számlálót akarjuk növelni: Gomb.kattintas += 1.
6. fejezet
Ellenőrzött attribútumok: @property
Mi akadályozza meg, hogy valaki egy téglalap oldalát negatívra állítsa (t.a = -5)? A @property dekorátorral egy attribútum olvasását és írását metódusok mögé rejthetjük: kívülről továbbra is t.a-nak látszik, de az értékadáskor lefut az ellenőrzés. Így számított attribútumot is készíthetünk (pl. a terület), amelyet mindig az aktuális oldalakból számolunk.
class Teglalap:
def __init__(self, a, b):
self.a = a
self.b = b
@property
def a(self):
return self._a
@a.setter
def a(self, ertek):
if ertek <= 0:
raise ValueError("Az oldal hossza pozitív szám legyen!")
self._a = ertek
@property
def b(self):
return self._b
@b.setter
def b(self, ertek):
if ertek <= 0:
raise ValueError("Az oldal hossza pozitív szám legyen!")
self._b = ertek
@property
def terulet(self):
return self.a * self.b
t = Teglalap(3, 4)
print(t.terulet)
t.a = 10
print(t.terulet)
t.b = 0
Figyeld meg: a konstruktor is a self.a = a formát használja, így már a létrehozáskor lefut az ellenőrzés (ha közvetlenül a „belső” _a-ba írna, megkerülné azt). A _a előtti aláhúzás megállapodás: „belső használatra, kívülről ne nyúlj hozzá”. A hibaüzenet pedig azt mondja, ami a szabály: pozitív legyen – a nulla sem megengedett.
raise
A raise ValueError("…") szándékosan kivételt (hibát) vált ki a megadott üzenettel. A program ott megáll – hacsak a hívó egy try–except blokkban (10. modul) el nem kapja. Így jelzi az osztály a hívónak, hogy érvénytelen értéket kapott, ahelyett hogy csendben hibás állapotba kerülne.
Dekorátor: a függvény fölötti @valami sor módosítja, hogyan viselkedjen az alatta álló függvény. A @property attribútummá „alakítja” a metódust. Két további gyakori dekorátor: a @staticmethod olyan függvényt jelöl, amely logikailag az osztályhoz tartozik, de nem használ self-et; a @classmethod pedig az osztályt kapja első paraméterként (cls), és tipikusan alternatív létrehozó módokhoz használjuk (pl. Homerseklet.fahrenheitbol(98.6)).
7. fejezet
Öröklődés
Az öröklődéssel egy meglévő osztályból (szülő, ősosztály) újat készítünk (gyerek, leszármazott). A gyerek mindent megkap, amit a szülő tud, ehhez hozzáadhat új attribútumokat és metódusokat, és felülírhatja (override) a szülő metódusait. A szülő metódusát a super()-rel érjük el – leggyakrabban a konstruktorban.
class Allat:
def __init__(self, nev):
self.nev = nev
def hang(self):
return "…"
def bemutatkozik(self):
return f"{self.nev} vagyok, és ezt mondom: {self.hang()}"
class Kutya(Allat):
def hang(self):
return "Vau!"
class Madar(Allat):
def __init__(self, nev, szarnyfesztav):
super().__init__(nev)
self.szarnyfesztav = szarnyfesztav
def hang(self):
return "Csip-csirip!"
def repul(self):
return f"{self.nev} repül ({self.szarnyfesztav} cm-es szárnyakkal)."
allatok = [Kutya("Bodri"), Madar("Csőrike", 25), Allat("Valaki")]
for a in allatok:
print(a.bemutatkozik())
print(allatok[1].repul())
print(isinstance(allatok[0], Kutya), isinstance(allatok[0], Allat))
Figyeld meg: a bemutatkozik() metódust csak az Allat-ban írtuk meg, mégis minden állat a saját hangját mondja, mert a self.hang() mindig az objektum tényleges osztályának metódusát hívja. Ezt nevezzük többalakúságnak (polimorfizmus): ugyanaz a hívás a különböző típusú objektumoknál különbözőképpen viselkedik. A Madar konstruktora a super().__init__(nev) hívással a szülőre bízza a név beállítását, és csak a saját új attribútumáról gondoskodik.
Egy osztálynak több szülője is lehet (többszörös öröklődés, pl. class DronRobot(Repulo, Robot):). A Python támogatja, de bonyolulttá teszi a programot; kezdő szinten ritkán van rá szükség.
8. fejezet
Gyakorló feladatok
A gép az osztályodat példányosítja és a metódusait hívja – az osztály, a metódusok és az attribútumok neve egyezzen a feladatban megadottal.
Diák osztály
Írj Diak osztályt, amelynek konstruktora a nevet (nev) és az osztályt (osztaly) kapja, és mindkettőt attribútumként tárolja. Legyen egy bemutat() metódusa, amely a tárolt névből és osztályból összeállított szöveget adja vissza, vesszővel elválasztva. Például a Diak("Kiss Anna", "10.B") objektumnál: Kiss Anna, 10.B.
class Diak:
pass
d = Diak("Kiss Anna", "10.B")
print(d.bemutat())
class Diak:
def __init__(self, nev, osztaly):
self.nev = nev
self.osztaly = osztaly
def bemutat(self):
return f"{self.nev}, {self.osztaly}"
d = Diak("Kiss Anna", "10.B")
print(d.bemutat())
Tipp
Konstruktor: def __init__(self, nev, osztaly):, benne self.nev = nev. A metódus első paramétere is self, és return-nel adja vissza a szöveget.
Jegyek nyilvántartása
Írj Tanulo osztályt: a konstruktor a nevet kapja (nev), és egy üres jegyek listát hoz létre. Metódusok: uj_jegy(jegy) felveszi a jegyet, ha 1 és 5 közé esik (feltételezheted, hogy egész számot kap), és True-t ad vissza, különben nem veszi fel, és False-t ad; atlag() a jegyek átlagát adja (üres listánál None-t). Minden tanulónak saját jegylistája legyen.
class Tanulo:
def __init__(self, nev):
self.nev = nev
self.jegyek = []
def uj_jegy(self, jegy):
if 1 <= jegy <= 5:
self.jegyek.append(jegy)
return True
return False
def atlag(self):
if not self.jegyek:
return None
return sum(self.jegyek) / len(self.jegyek)
anna = Tanulo("Anna")
anna.uj_jegy(5)
anna.uj_jegy(4)
print(anna.nev, anna.jegyek, anna.atlag())
Tipp
A listát az __init__-ben hozd létre: self.jegyek = []. Ha az osztály törzsében hoznád létre (osztályváltozóként), minden tanuló ugyanazt a listát használná.
Hőmérséklet két skálán
Írj Homerseklet osztályt, amely belül Celsius-fokban tárol (konstruktor: celsius=0). Legyen celsius és fahrenheit property-je, mindkettő olvasható és írható (F = C · 9/5 + 32). Az abszolút nulla (−273,15 °C) alatti értéket a celsius setter ValueError-ral utasítsa el.
h = Homerseklet(100) h.fahrenheit → 212.0 h.fahrenheit = 32 h.celsius → 0.0
class Homerseklet:
def __init__(self, celsius=0):
self.celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, ertek):
if ertek < -273.15:
raise ValueError("Az abszolút nulla alatti hőmérséklet nem létezik.")
self._celsius = ertek
@property
def fahrenheit(self):
return self._celsius * 9 / 5 + 32
@fahrenheit.setter
def fahrenheit(self, ertek):
self.celsius = (ertek - 32) * 5 / 9
h = Homerseklet(100)
print(h.fahrenheit)
h.fahrenheit = 32
print(h.celsius)
Tipp
A fahrenheit setter ne a _celsius-ba írjon közvetlenül, hanem a self.celsius = … formával – így az ellenőrzés itt is lefut. A konstruktor is ezt a formát használja.
Rendezhető könyvek
Írj Konyv osztályt (cim, szerzo, oldal). A str() adja ezt: Molnár Ferenc: Pál utcai fiúk (240 oldal). Két könyv legyen egyenlő, ha a címük és a szerzőjük azonos, és a < az oldalszám szerint hasonlítson (így a sorted() oldalszám szerint rendez).
class Konyv:
def __init__(self, cim, szerzo, oldal):
self.cim = cim
self.szerzo = szerzo
self.oldal = oldal
def __str__(self):
return f"{self.szerzo}: {self.cim} ({self.oldal} oldal)"
def __eq__(self, masik):
return self.cim == masik.cim and self.szerzo == masik.szerzo
def __lt__(self, masik):
return self.oldal < masik.oldal
k = Konyv("Pál utcai fiúk", "Molnár Ferenc", 240)
print(k)
Tipp
Három speciális metódus kell: __str__, __eq__ és __lt__. Mindegyik a self és a masik attribútumait veti össze.
Számlatípusok örökléssel
Adott a Szamla ősosztály. Készíts belőle két leszármazottat, amelyek a kivesz(osszeg) metódust felülírják:
MegtakaritasiSzamla(egyenleg=0, minimum=1000): csak akkor enged kivenni, ha utána legalábbminimummarad a számlán;HitelSzamla(egyenleg=0, hitelkeret=100000): az egyenleg-hitelkeret-ig mehet le.
Mindkettő konstruktora a super().__init__(egyenleg) hívással állítsa be az egyenleget. A kivesz sikeres levételnél True-t, egyébként False-t adjon (és ne változtasson az egyenlegen); nem pozitív összeget soha ne fogadjon el.
class Szamla:
def __init__(self, egyenleg=0):
self.egyenleg = egyenleg
def befizet(self, osszeg):
if osszeg > 0:
self.egyenleg += osszeg
def kivesz(self, osszeg):
if 0 < osszeg <= self.egyenleg:
self.egyenleg -= osszeg
return True
return False
# Ide jön a két leszármazott osztály
class Szamla:
def __init__(self, egyenleg=0):
self.egyenleg = egyenleg
def befizet(self, osszeg):
if osszeg > 0:
self.egyenleg += osszeg
def kivesz(self, osszeg):
if 0 < osszeg <= self.egyenleg:
self.egyenleg -= osszeg
return True
return False
class MegtakaritasiSzamla(Szamla):
def __init__(self, egyenleg=0, minimum=1000):
super().__init__(egyenleg)
self.minimum = minimum
def kivesz(self, osszeg):
if osszeg > 0 and self.egyenleg - osszeg >= self.minimum:
self.egyenleg -= osszeg
return True
return False
class HitelSzamla(Szamla):
def __init__(self, egyenleg=0, hitelkeret=100000):
super().__init__(egyenleg)
self.hitelkeret = hitelkeret
def kivesz(self, osszeg):
if osszeg > 0 and self.egyenleg - osszeg >= -self.hitelkeret:
self.egyenleg -= osszeg
return True
return False
m = MegtakaritasiSzamla(5000)
h = HitelSzamla(0, 20000)
print(m.kivesz(4500), m.kivesz(3000), m.egyenleg)
print(h.kivesz(15000), h.kivesz(10000), h.egyenleg)
Tipp
class MegtakaritasiSzamla(Szamla): – a zárójelben a szülő. A konstruktorban előbb super().__init__(egyenleg), aztán a saját attribútum. A kivesz feltételében a „kivét utáni” egyenleget vizsgáld: self.egyenleg - osszeg.
9. fejezet
Összefoglalás
| Fogalom | Lényeg |
|---|---|
class Nev: | osztály = tervrajz; Nev(…) új objektumot (példányt) hoz létre |
__init__(self, …) | konstruktor: beállítja az új objektum attribútumait |
self | az aktuális objektum; a metódusok első paramétere, hívásnál nem adjuk át |
| példány- / osztályváltozó | self.x objektumonként; az osztály törzsében: közös |
__str__, __eq__, __lt__, __add__ | kiírás, ==, < (rendezés), + |
@property | ellenőrzött vagy számított attribútum |
class Gyerek(Szulo):, super() | öröklődés, felülírás, a szülő metódusának hívása |
Gyakorold tovább: modellezz egy egyszerű könyvtárat: Konyv (cím, szerző, kölcsönözve-e) és Konyvtar (könyvek listája; felvesz, kolcsonoz(cim), visszahoz(cim), elerheto() metódusok). Gondold végig: melyik adat melyik osztályhoz tartozik, és mit adjon vissza egy-egy metódus, ha a könyv nem található?