Kihagyás

Osztályok, objektumok

A Python nyelvi szinten támogatja az objektumorientált programozást. Ennek köszönhetően a korábban (Programozás I és Webtervezés kurzusokon) megismert objektumorientált koncepciók Pythonban is megvalósíthatók. Ezen a gyakorlaton megnézzük, hogy hogyan.

Osztály létrehozása

Az osztály absztrakt fogalom, konkrét életbeli objektumok, objektumcsoportok formai leírása. Osztályokkal olyan objektumokat formalizálhatunk, amelyek azonos tulajdonságokkal és viselkedéssel rendelkeznek.

Pythonban osztályt a következő szintaxissal hozhatunk létre:

1
2
class OsztalyNeve(object):
    osztály törzse...

Közvetlenül az osztály neve után, zárójelek között megadhatjuk, hogy melyik osztályból öröklődünk. A Python (Javával ellentétben) támogatja a többszörös öröklődést, így egy osztály akár egyszerre több másik osztályból is öröklődhet.

A Pythonbeli öröklődési hierarchia tetején az object névre hallgató ősosztály áll. Python 3-ban, ha ebből az object ősosztályból származtatunk egy osztályt, akkor ezt nem szükséges expliciten kiírnunk, így class OsztalyNeve(object) helyett azt is írhatjuk, hogy class OsztalyNeve.

Megjegyzés

A Python 2-vel való kompatibilitás miatt korábban célszerű volt kiírni az (object)-et az osztályunk neve után. Mivel a Python 2 már nem támogatott, így ez ma már elhagyható, de régebbi kódokban találkozhatunk ilyen jelöléssel.

Példa: A Szuperhos osztály elkezdése

1
2
class Szuperhos:
    pass

A pass utasítás

A fenti kódpéldában látható pass utasítás azt jelzi, hogy az osztályunk törzse üres, ezt majd később fogjuk megírni.

Pythonban egy blokkot (vezérlési ágat, függvénytörzset, osztálynak a törzsét stb.) nem hagyhatunk üresen - ekkor hibát kapnánk. Ha a blokkunk nem tartalmaz utasítást, akkor a pass utasítást kell beleírnunk - ezzel jelezzük, hogy az adott blokkban nem csinálunk semmit.

Adattagok, metódusok

Az osztályok adattagokat és metódusokat (tagfüggvényeket) tartalmazhatnak. Ezeket a . (pont) operátor segítségével tudjuk elérni.

Pythonban az adattagokat nem deklaráljuk külön az osztályban (mint ahogy azt Javában csináltuk), hanem a konstruktoron belül hozzuk létre és inicializáljuk őket.

A metódusok lényegében függvények az osztályon belül, így a def kulcsszóval hozzuk létre őket. Egy fontos szabály, hogy Pythonban minden metódusnak van egy kötelező első paramétere, amit expliciten ki kell írni minden metódus fejlécében. Ezt általában self-nek nevezzük el, és ezzel magára az objektumra tudunk hivatkozni (kb. olyan, mint Javában a this).

Megjegyzés

Az aktuális objektumra hivatkozó azonosítót nem kötelező self-nek elnevezni, a név tetszőleges is lehet.

Példa: Metódus létrehozása az osztályon belül

1
2
3
class OsztalyNeve:
    def metodusNeve(self, param):   # a self után jönnek a "tényleges" paraméterek
        print("Ez egy metódus a következő paraméterrel:", param)

Konstruktor

A konstruktor az osztály példányosítása során (tehát a konkrét objektumpéldányok létrehozásakor) lefutó, speciális metódus. Pythonban az __init__ metódus tekinthető konstruktornak, ezt használjuk az adattagok létrehozására és inicializálására.

A konstruktor szintaxisa (a szögletes zárójelek közötti részek elhagyhatók):

1
2
3
class OsztalyNeve:
    def __init__(self, [param1, param2, ...]):
        konstruktor utasítások... (pl. adattagok inicializálása)

Példa: A szuperhősökről eltároljuk a nevüket és a szupererejüket. Hozzunk létre egy olyan konstruktort a Szuperhos osztályban, amely paraméterül kapja a nevet és a szupererőt, és ezekkel az értékekkel inicializálja az adattagokat!

1
2
3
4
class Szuperhos:
    def __init__(self, nev, szuperero):
        self.nev = nev                  # adattagok létrehozása és inicializálása
        self.szuperero = szuperero

Pythonban továbbra sincs function overload. Ha azt szeretnénk elérni, hogy egy metódust többféle, eltérő paraméterezéssel is tudjunk használni, akkor használjuk a default függvényparamétereket.

Példa: Írjuk át a Szuperhos osztály konstruktorát úgy, hogy a szupererő paraméter értékét ne legyen kötelező megadni, alapértéke legyen 50!

1
2
3
4
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self.nev = nev
        self.szuperero = szuperero

Objektumok létrehozása

A példányosítás során az osztályból objektumpéldányt készítünk. Tehát az általános formai leírásunknak (ami az osztály) egy konkrét példányát gyártjuk le.

A példányosítás során meghívjuk a példányosítani kívánt osztály konstruktorát, amelynek rendre átadjuk a szükséges paramétereket (az első, self paramétert nem adjuk át).

A példányosítás szintaxisa (a szögletes zárójelek közötti részek elhagyhatók):

1
objektumNeve = OsztalyNeve([param1, param2, ...])

Példa: Példányosítsuk a Szuperhos osztályunkat!

1
2
3
4
5
6
7
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self.nev = nev
        self.szuperero = szuperero
        print("-- Szuperhős létrehozva. --")  # a szemléletesség kedvéért :)

hos1 = Szuperhos("Thor", 70)

Kimenet

-- Szuperhős létrehozva. --

Láthatóságok, getterek, setterek, property-k

Javában az adattagok és metódusok elérhetőségét különböző láthatósági módosítószavakkal tudtuk megadni (public, protected, private, "package private" láthatóságok).

Pythonban nincsenek a láthatóság szabályozására szolgáló módosítószavak.

Konvenció alapján az adattag neve előtti egyszeres alulvonás azt jelzi, hogy az adattag nem publikus használatra van szánva ("private adattag"). Viszont ettől az adattag kívülről továbbra is elérhető lesz!

Példa: Jelezzük, hogy a Szuperhos osztály adattagjait nem publikus használatra szánjuk!

1
2
3
4
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self._nev = nev             
        self._szuperero = szuperero

Pythonban is készíthetünk az adattagokhoz hagyományos getter, illetve setter metódusokat.

Emlékeztetőül: a getter az adattagok értékének lekérdezésére, míg a setter az adattagok értékének beállítására szolgáló metódus. Ezeket nem publikus adattagok esetén használjuk.

Példa: Írjunk hagyományos gettert és settert a Szuperhos osztály _szuperero adattagjához!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self._nev = nev 
        self._szuperero = szuperero

    def get_szuperero(self):          # getter metódus
        return self._szuperero

    def set_szuperero(self, ertek):   # setter metódus
        self._szuperero = ertek

# === példányosítás ===

hos1 = Szuperhos("Thor", 70)
hos1.set_szuperero(100)               # setter hívás
print(hos1.get_szuperero())           # getter hívás

Kimenet

100

Pythonban viszont a fenti szintaxis ritkán használatos. Ehelyett általában úgynevezett property-ket szoktunk használni getterek és setterek megvalósítására.

A get property szintaxisa:

1
2
3
@property
def adattag1(self):
    return self._adattag1

A set property szintaxisa:

1
2
3
@adattag1.setter
def adattag1(self, ertek):
    self._adattag1 = ertek

Példa: Cseréljük le a Szuperhos osztályban a _szuperero adattaghoz készített hagyományos gettert és settert property-kre! Figyeljük meg, hogy mennyivel egyszerűbb a property-k használata az osztály példányosítása után a hagyományos getter/setter függvényekhez képest!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self._nev = nev 
        self._szuperero = szuperero

    @property
    def szuperero(self):            # get property
        return self._szuperero

    @szuperero.setter
    def szuperero(self, ertek):     # set property
        self._szuperero = ertek

# === példányosítás ===

hos1 = Szuperhos("Thor", 70)
hos1.szuperero = 100                  # set property hívás
print(hos1.szuperero)                 # get property hívás

Kimenet

100

Figyelem

Fontos, hogy a property és az adattag neve mindig eltérő legyen, mert különben végtelen rekurzióba futunk! A fenti példában a get és set property-k neve szuperero (alulvonás nélkül), az adattag neve pedig ettől eltérő módon _szuperero (alulvonással).

Feladat

A fenti mintájára készítsünk get és set property-t a _nev adattaghoz is!

Objektumok kiíratása

Ha Pythonban a print() függvénnyel kiíratunk egy objektumot, akkor valami ehhez hasonlót kapunk a konzolon: <_ _main_ _.OsztalyNeve object at 0x012B08D0>.

Hát, ez közel sem szép. Szerencsére ezen lehetőségünk van szépíteni, ha az osztályon belül felüldefiniáljuk az __str__ metódust. Ez a metódus az objektum szöveggé alakítását valósítja meg (mint Javában a toString()). Visszatérési értéke egy string, ezt írjuk felül.

1
2
3
4
5
class OsztalyNeve:
    # ...

    def __str__(self):
        return "Ez a szöveg fog megjelenni az objektum kiíratásakor."

Példa: Írjuk meg a Szuperhos osztályban a szöveggé alakítást megvalósító metódust! A metódus írja ki az adattagok értékét!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self._nev = nev 
        self._szuperero = szuperero

    # ...

    def __str__(self):
        return self._nev + " egy szuperhős, akinek szuperereje " + str(self._szuperero)

# === példányosítás ===

hos1 = Szuperhos("Thor", 70)
print(hos1)         

Kimenet

Thor egy szuperhős, akinek szuperereje 70

Operator overloading

Az operator overloading segítségével kiterjeszthetjük a megszokott operátoraink működését.

Például a + (plusz) operátort Pythonban használhatjuk két egész szám összeadására, illetve két string összefűzésére is. Ez azért lehetséges, mert a + operátor ki van terjesztve az int és az str osztályokban egyaránt. Amikor egy operátor különböző osztályok példányaira használva másként viselkedik, operator overloading-nak nevezzük.

Pythonban a saját osztályainkban is lehetőségünk van bizonyos operátorok működését felüldefiniálnunk, ha felülírjuk a nekik megfelelő metódus működését.

Néhány speciális metódus, és az operátorok, amelyeket felüldefiniálhatunk vele:

Operator overload függvény A függvényt meghívó kifejezés
__eq__ (egyenlőség) obj1 == obj2
__ne__ (nem egyenlőség) obj1 != obj2
__add__ (összeadás) obj1 + obj2
__sub__ (kivonás) obj1 - obj2
__iadd__ (megnövelés) obj1 += obj2
__isub__ (csökkentés) obj1 -= obj2
__lt__ (kisebb, mint) obj1 < obj2
__gt__ (nagyobb, mint) obj1 > obj2
__le__ (kisebb vagy egyenlő) obj1 <= obj2
__ge__ (nagyobb vagy egyenlő) obj1 >= obj2

Példa: Definiáljuk felül az == és + operátorok működését a Szuperhos osztályban!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self._nev = nev 
        self._szuperero = szuperero

    # ...

    def __str__(self):
        return self._nev + " egy szuperhős, akinek szuperereje " + str(self._szuperero)

    # két szuperhős akkor lesz egyenlő, ha a nevük és a szupererejük megegyezik

    def __eq__(self, masik_hos):
        return self._nev == masik_hos._nev and self._szuperero == masik_hos._szuperero

    # két szuperhős összeadása során a szupererejük összeadódik

    def __add__(self, masik_hos):
        uj_szuperero = self._szuperero + masik_hos._szuperero
        uj_szuperhos = Szuperhos("Megahős", uj_szuperero)
        return uj_szuperhos

# === tesztelés ===

hos1 = Szuperhos("Thor", 70)
hos2 = Szuperhos("Hulk", 80)
hos3 = Szuperhos("Hulk", 80)
hos4 = hos1 + hos2
print(hos2 == hos3)
print(hos4)         

Kimenet

True Megahős egy szuperhős, akinek szuperereje 150

Típusellenőrzés

Mivel a Python nem fordított nyelv, így statikus típusellenőrzés nincs benne. Ez aggasztó lehet, ha szeretnénk ellenőrizni, hogy a metódusunk egy bizonyos típusú paramétert kapott-e (például egy Szuperhos objektumot).

Sajnos ezt csak dinamikusan, futásidőben tudjuk ellenőrizni, az isinstance(obj, type) függvénnyel. A függvény pontosan akkor ad vissza igazat, ha az obj objektum type típusú.

Példa: Az __add__ metódus utasításait csak Szuperhos típusú paraméter esetén hajtsuk végre! Eltérő típus esetén írassunk ki hibaüzenetet!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Szuperhos:
    def __init__(self, nev, szuperero=50):
        self._nev = nev 
        self._szuperero = szuperero

    # ...

    def __add__(self, masik_hos):
        if isinstance(masik_hos, Szuperhos):   # típusellenőrzés
            uj_szuperero = self._szuperero + masik_hos._szuperero
            uj_szuperhos = Szuperhos("Megahős", uj_szuperero)
            return uj_szuperhos
        else:
            print("Egy szuperhőst csak egy másik szuperhőssel lehet összeadni.")

Az isinstance() függvényt beépített típusokra is használhatjuk.

1
2
print(isinstance(42, int))  
print(isinstance(42, str))  

Kimenet

True False

A Szuperhos osztály befejezése

Feladat

A szuperhősöknek legyen egy kepessegek adattagja is, ami kezdetben egy üres lista! Definiáljuk felül a += operátort, ami a paraméterül kapott értéket beszúrja a kepessegek lista végére, amennyiben az szöveges típusú!

A végleges kódunk

Az így kapott, végleges kódunk:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Szuperhos:
    def __init__(self, nev, szuperero=50):  # konstruktor
        self._nev = nev
        self._szuperero = szuperero
        self.kepessegek = []

    @property
    def nev(self):                          # get property a _nev adattaghoz
        return self._nev

    @nev.setter
    def nev(self, ertek):                   # set property a _nev adattaghoz
        self._nev = ertek

    @property
    def szuperero(self):                    # get property a _szuperero adattaghoz
        return self._szuperero

    @szuperero.setter
    def szuperero(self, ertek):             # set property a _szuperero adattaghoz
        self._szuperero = ertek

    def __iadd__(self, kepesseg):
        if isinstance(kepesseg, str):         # típusellenőrzés
            self.kepessegek.append(kepesseg)
            return self
        else:
            print("A képesség szöveges adat kell, hogy legyen.")

    def __str__(self):                      # szöveggé alakítást megvalósító metódus
        return self.nev + " egy szuperhős, akinek szuperereje " + str(self.szuperero)

    def __eq__(self, masik_hos):            # == operátor felüldefiniálása
        if not isinstance(masik_hos, Szuperhos):
            return False

        return self.nev == masik_hos.nev and self.szuperero == masik_hos.szuperero

    def __add__(self, masik_hos):           # + operátor felüldefiniálása
        if isinstance(masik_hos, Szuperhos):
            uj_szuperero = self.szuperero + masik_hos.szuperero
            uj_szuperhos = Szuperhos("Megahős", uj_szuperero)
            return uj_szuperhos
        else:
            print("Egy szuperhőst csak egy másik szuperhőssel lehet összeadni.")

# === tesztelés ===

hos1 = Szuperhos("Thor", 70)              # példányosítás
hos2 = Szuperhos("Hulk")
hos3 = hos1 + hos2                        # __add__ hívás
hos4 = Szuperhos("Megahős", 120)

print(hos1.szuperero)                     # get property hívás
hos1.szuperero = 100                      # set property hívás
print(hos1)                               # __str__ hívás
print(hos2)
print(hos3)
print(hos4)
print(hos3 == hos4)                       # __eq__ hívás

hos1 += "repülés"                         # __iadd__ hívás
hos1 += "idojárás befolyásolása"
print(hos1.kepessegek)

A fenti kód kimenete:

1
2
3
4
5
6
7
70
Thor egy szuperhős, akinek szuperereje 100
Hulk egy szuperhős, akinek szuperereje 50
Megahős egy szuperhős, akinek szuperereje 120
Megahős egy szuperhős, akinek szuperereje 120
True
['repülés', 'idojárás befolyásolása']

Feladatok

Az anyagrészhez tartozó gyakorló feladatsor elérhető itt (a feladatsor megoldásához a következő leckében tárgyalt kivételkezelés ismerete is szükséges).


Utolsó frissítés: 2022-09-05 11:41:50