Kihagyás

4. gyakorlat

A gyakorlat anyaga

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ályok, objektumok

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 megadjuk, 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 kiírnunk: class OsztalyNeve(object) helyett írhatunk rövidebben annyit, hogy class OsztalyNeve.

Megjegyzés

A Python 2-vel való kompatibilitás miatt célszerű kiírni az (object)-et az osztályunk neve után, még akkor is ha az elhagyható lenne. Ebben a jegyzetben is mindig kiírjuk ezt.

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

1
2
class Szuperhos(object):
    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. Ez a self kulcsszó, aminek segítségével 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(object):
    def metodusNeve(self, param):
        print("Ez egy metódus a következő paraméterrel:", param)
Konstruktor

A konstruktor az osztály példányosítása során 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(object):
    def __init__(self, [param1, param2, ...]):
        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 Szuperhős 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(object):
    def __init__(self, nev, szuperero):
        self.nev = nev
        self.szuperero = szuperero

Pythonban továbbra sincs function overload. Ha azt szeretnénk elérni, hogy egy függvényt 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 Szuperhős 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(object):
    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).

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 Szuperhős osztályunkat!

1
2
3
4
5
6
7
class Szuperhos(object):
    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álatatra 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 Szuperhős osztály adattagjait nem publikus használatra szánjuk!

1
2
3
4
class Szuperhos(object):
    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 függvényeket.

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 Szuperhős osztály szupererő adattagjához!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Szuperhos(object):
    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

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 Szuperhős osztályban a szupererő adattaghoz készített hagyományos gettert és settert property-kre!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Szuperhos(object):
    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

Figyelem

Fontos, hogy a property és az adattag neve mindig eltérő legyen, különben végtelen rekurzióba futunk! A példában a property-k neve szuperero (alulvonás nélkül), az adattag neve 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(object):
    # ...

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

Példa: Írjuk meg a Szuperhős 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(object):
    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
__neq__ (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 Szuperhős 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(object):
    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 Szuperhős 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 Szuperhős 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(object):
    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 instanceof() 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 kész program

Feladat: A szuperhősöknek legyen egy kepessegek adattagja is, ami kezdetben egy üres lista! Írjunk egy kepesseget_felvesz metódust, ami a paraméterül kapott értéket (amennyiben az szöveges típusú) beszúrja a kepessegek lista végére!

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
class Szuperhos(object):
  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 kepesseget_felvesz(self, kepesseg):
    if isinstance(kepesseg, str):         # típusellenőrzés
      self.kepessegek.append(kepesseg)
    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)                     # getter hívás
hos1.szuperero = 100                      # setter hívás
print(hos1)                               # __str__ hívás
print(hos2)
print(hos3)
print(hos4)
print(hos3 == hos4)                       # __eq__ hívás

Kimenet

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


Utolsó frissítés: 2020-10-23 12:32:44