Objektumorientált programozás II.

Egységbezárás (encapsulation)
a procedurális megközelítéssel ellentétben az adatok és a függvények a program nem különálló részét képezik, azok összetartoznak: együtt alkotnak egy objektumot. Az objektum felülete(ke)n (interfész, interface) keresztül érhető el a többi objektum számára.
Osztály (class)
egy objektum prototípusa, tulajdonságokkal ellátva, melyek jellemzik az osztály példányait. A tulajdonságok osztály- és példányváltozók és metódusok (tagfüggvények); pont-jelöléssel érhetők el.
Attribútum/jellemző (attribute)
egy objektum jellemzője: az objektum nevét követő pont után írjuk a nevét.
Példány (instance)
egy osztályhoz tartozó egyedi objektum.
Példányosítás (instantiation)
egy osztály egy példányának létrehozása.
Példány változó/tagváltozó (instance variable)
metóduson belül definiált változó, mely csak az osztály adott példányához tartozik.
Osztályváltozó (class variable)
változó, melyet az osztály minden példánya elér.
Metódus (method, tagfüggvény)
osztályon belül definiált olyan függvény, melynek első argumentuma a self.
Öröklődés (inheritance)
származtatással egy már meglévő osztályból (ős, alaposztály) egy újat hozunk létre (alosztány, subclass). Ez rendelkezni fog az ős minden tulajdonságával, amihez továbbiakat ad(hat)unk. (E lehetőség használata a szakosítás, specializáció.)
Polimorfizmus/többalakúság (polymorphism)
különböző viselkedésmódokkal ruházzuk fel az egymásból származtatott objektumokat, pl. egy alosztályban átírhatjuk az ősosztályban megírt metódust azonos névvel, és a Python az objektumtól függően a megfelelő változatot fogja használni.

Öröklődés

Írunk egy Person osztályt. Minden embernek lesz neve és titulusa, a __str__ függvénnyel pedig megszólítjuk. A Knight osztály a Person leszármazottja. Minden lovag egyben ember is, de a megszólításuk egyedi. A leszármazott örökli az ősosztály összes tagváltozóját és metódusát.

In [1]:
class Person:
    def __init__(self, name, title):
        self.name = name
        self.title = title
    def __str__(self):
        return self.title + " " + self.name

class Knight(Person):
    def __init__(self, name):
        super().__init__(name, 'Sir')

varga = Person('Varga', 'Mr')
launcelot = Knight('Launcelot')
print(varga)
print(launcelot)
Mr Varga
Sir Launcelot

A leszármazott osztályban definiálhatunk új tagváltozókat is. Például legyen minden lovagnak egy opcionális epitheton ornansa!

In [2]:
class Person:
    def __init__(self, name, title):
        self.name = name
        self.title = title
    def __str__(self):
        return self.title + " " + self.name

class Knight(Person):
    def __init__(self, name, eo=""):
        super().__init__(name, 'Sir')
        self.eo = eo
         
launcelot = Knight('Launcelot', 'the brave')
print(launcelot)
Sir Launcelot

Minden lovag megérdemli, hogy a neve mellé az állandó jezőjét is hozzáfűzzük. Ehhez felüldefiniáljuk a __str__ metódust. Azonos nevű metódusok esetén mindig a leszármazottban definiált élvez elsőbbséget és hívódik meg.

In [3]:
class Person:
    def __init__(self, name, title):
        self.name = name
        self.title = title

    def __str__(self):
        return self.title + " " + self.name

class Knight(Person):
    def __init__(self, name, eo=""):
        super().__init__(name, 'Sir')
        self.eo = eo

    def __str__(self):
        if len(self.eo) > 0:
            return self.title + " " + self.name + ", " + self.eo
        else:
            return super().__str__()
         
launcelot = Knight('Launcelot', 'the brave')
black = Knight('Black')
robin = Knight('Robin', 'the Not-quite-so-brave-as-Sir-Launcelot')
print(launcelot)
print(black)
print(robin)
Sir Launcelot, the brave
Sir Black
Sir Robin, the Not-quite-so-brave-as-Sir-Launcelot

Példányváltozók, osztályváltozók

Van mód arra is, hogy bizonyos változókat osztályszinten definiáljunk. Ilyen például a lovagok "Sir" megszólítása. Hogy ne essen egybe a Person title változójával, nevezzük special_title-nak.

Ahhoz, hogy az osztályszintű változót elérjük, a Knight.special_title-ra kell hivatkozni, de bármelyik példány .special_title változója is erre mutat.

In [4]:
class Person:
    def __init__(self, name, title):
        self.name = name
        self.title = title
    def __str__(self):
        return self.title + " " + self.name

class Knight(Person):
    special_title = 'Sir'
    def __init__(self, name, eo=""):
        super().__init__(name, "")
        self.eo = eo
    def __str__(self):
        return Knight.special_title + " " + self.name + ", " + self.eo
         
launcelot = Knight('Launcelot', 'the brave')
robin = Knight('Robin', 'the Not-quite-so-brave-as-Sir-Launcelot')
print(launcelot)
print(robin)
Sir Launcelot, the brave
Sir Robin, the Not-quite-so-brave-as-Sir-Launcelot
In [5]:
print(robin.special_title, launcelot.special_title, Knight.special_title)
Sir Sir Sir

Megjegyezzük, hogy az öröklési lánc tetszőlegesen hosszú lehet, illetve lehet egyszerre több osztályból örökölni.

Röviden a kivételekről

Vannak olyan esetek, amikor a kód hibába ütközik. Ilyenkor egy Exception típusú objektumot dob (emel) a Python.

Ekkor

  • a kód nem folytatja szokványos futását
  • minden függvény azonnal kilép
  • ha nem függvényen belül történik, akkor a kód megáll azon a ponton

Kivéve, ha

  • lekezeljük (elkapjuk) a kivételt egy
    try:
            ...
        except ... :
            ...
    
    blokkal

Egy Exception-t bármikor el lehet kapni, akármennyi függvényhívást túlél. Nézzünk egy példát! Most mi magunk hívjuk elő a hibát (raise), de egyben le is kezeljük.

In [6]:
try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))    
    print(inst.args)      
    print(inst)           
    x, y = inst.args
    print('x =', x)
    print('y =', y)
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

Most pedig lekezelünk egy nem mesterségesen létrehozott kivételt.

In [7]:
try:
    str(5) + 5
except TypeError as inst:
    print(type(inst))
    print(inst.args)     
    print(inst)
<class 'TypeError'>
('must be str, not int',)
must be str, not int

Lássunk egy természetes példát! Olvassunk be egy valós számot és írjuk ki a kétszeresét! Ha a felhasználó nem valós számot ad, a float() nem tudja azzá konvertálni, így hibát jelez, mi viszont ezt lekezeljük.

In [8]:
while True:
    try:
        x = float(input("Adjon meg egy valós számot: "))
        break
    except ValueError:
        print("Hoppá! Ez nem valós szám, próbálja újra!")
print(2*x)
Adjon meg egy valós számot: valós szám
Hoppá! Ez nem valós szám, próbálja újra!
Adjon meg egy valós számot: 12e23
2.4e+24

Kivételek függvényhívások között

Nézzük meg hogy hol történt a kivétel dobása (raise) és hol az elkapása.

In [9]:
def f(x):
    return x + 5 # nem jó ha x egy string

def g(x):
    return x + x # ez jó int-re és str-re is

x = "5"
y = g(x)
z = f(y)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-1b20a8445e65> in <module>
      7 x = "5"
      8 y = g(x)
----> 9 z = f(y)

<ipython-input-9-1b20a8445e65> in f(x)
      1 def f(x):
----> 2     return x + 5 # nem jó ha x egy string
      3 
      4 def g(x):
      5     return x + x # ez jó int-re és str-re is

TypeError: must be str, not int
In [10]:
def beolvas(n):
    maximum = float("-inf")
    for i in range(n):
        x = float(input())
        if x > maximum:
            maximum = x
    return maximum

try:
    y = beolvas(3)
    print(y)
except ValueError as e:
    print(e)
12
qwert
could not convert string to float: 'qwert'

Saját kivételek

Írhatunk saját kivétel osztályt is, ezt arra tudjuk használni, hogy egyedi hibákat jelezzünk vele.

Csupán az Exception beépített osztályból kell öröklődni (opcionálisan mást is lehet bele rakni).

In [11]:
class KnightException(Exception):
    pass

try:
    lancelot = Person("Lancelot", "Mr")
    x = str(lancelot)
    if x[:3] != "Sir":
        raise KnightException("Use correct title")    
except KnightException as s:
    print(s)
Use correct title

Iterálható (bejárható) és iterátor (bejáró) objektumok

Láttuk, hogy a for i in L nem csak akkor működik, ha L lista. Pontosan mely objektumok állhatnak a for ... in után?

A for meghívja az iter() függvényt, mellyel hozzájut az iterátorhoz (bejáró). Ebben definiálva van egy __next__() metódus, amely minden meghívására visszaadja az objektum egy elemét. Ha a __next__() nem talál több elemet, egy StopIteration kivételt dob.

Iterátor az az objektum, aminek van __next__() metódusa. Minden iterátor objektum egyúttal iterálható objektum. Fordítva ez nem áll. A lista iterálható, de nem iterátor. for ... in után iterálható objektum állhat, aminek van __iter__() metódusa, ami iterátor objektumot ad vissza. Ezek meghívhatók függvényként: x.__iter__() ekvivalens az iter(x), x.__next__() a next(x) hívással.

Ezek speciális metódusok!

In [12]:
r = range(3)    # iterálható (bejárható)
print(type(r))
it = iter(r)    # iterátor (bejáró)
print(type(it))
<class 'range'>
<class 'range_iterator'>
In [13]:
next(it)
Out[13]:
0
In [14]:
print(next(it))
print(next(it))
1
2
In [15]:
next(it)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-15-bc1ab118995a> in <module>
----> 1 next(it)

StopIteration: 

Tetszőleges, általunk definiált osztályhoz is adható iterátor tulajdonság. Először is szükség van egy __iter__ függvényre, ami egy olyan objektummal tér vissza, ami iterátor, ez jelzi az iterálás kezdetét. Az iterátor objektumok definiálnak egy __next__() metódust, ami sorban visszaadja az elemeket.

Ha kifogyott, akkor StopIteration kivételt emel.

In [16]:
class Person:
    def __init__(self, name, title=""):
        self.name = name
        self.title = title
    def __str__(self):
        return self.title + " " + self.name

class Knight(Person):
    title = 'Sir'
    searching_for = 'The Holy Grail'
    def __init__(self, name, eo):
        super().__init__(name)
        self.eo = eo
    def __str__(self):
        return Knight.title + " " + self.name + ", " + self.eo    

class Group:
    def __init__(self, name, persons):
        self.persons = persons
        self.name = name
        
    def __iter__(self):
        self.index = 0
        return self
    
    def __next__(self):
        if self.index >= len(self.persons):
            raise StopIteration     # dobunk egy kivételt
        self.index += 1
        return self.persons[self.index - 1]
        
kotrt = Group('Knights of The Round Table', 
              [Knight('Launcelot', 'the brave'), 
               Knight('Galahad', 'the pure'),
               Knight('Bedevere', 'the wise'), 
               Knight('Robin', 'the Not-quite-so-brave-as-Sir-Launcelot')])
for knight in kotrt:
    print(knight)
Sir Launcelot, the brave
Sir Galahad, the pure
Sir Bedevere, the wise
Sir Robin, the Not-quite-so-brave-as-Sir-Launcelot

Mindezt megoldhatjuk egyszerűbben a listát mint bejárható objektumot használva:

In [17]:
class Group:
    def __init__(self, name, persons):
        self.persons = persons
        self.name = name

    def __iter__(self): # A Group csoportot iterálhatóvá tesszük
        return iter(self.persons)


kotrt = Group('Knights of The Round Table',
              [Knight('Launcelot', 'the brave'),
               Knight('Galahad', 'the pure'),
               Knight('Bedevere', 'the wise'),
               Knight('Robin', 'the Not-quite-so-brave-as-Sir-Launcelot')])
for knight in kotrt:  # a ciklus lefut egy Group objektumra
    print(knight)
Sir Launcelot, the brave
Sir Galahad, the pure
Sir Bedevere, the wise
Sir Robin, the Not-quite-so-brave-as-Sir-Launcelot

Ha a Group objektumot nem tesszük bejárhatóvá, akkor még mindig kiírhatjuk egy csoport összes személyét, hisz a .persons tagváltozó lista (de ez már nem olyan elegáns):

In [18]:
class Group:
    def __init__(self, name, persons):
        self.persons = persons
        self.name = name

        
kotrt = Group('Knights of The Round Table', 
              [Knight('Launcelot', 'the brave'), 
               Knight('Galahad', 'the pure'),
               Knight('Bedevere', 'the wise'), 
               Knight('Robin', 'the Not-quite-so-brave-as-Sir-Launcelot')])
for knight in kotrt.persons: # kotrt nem, de kotrt.persons bejárható
    print(knight)
Sir Launcelot, the brave
Sir Galahad, the pure
Sir Bedevere, the wise
Sir Robin, the Not-quite-so-brave-as-Sir-Launcelot
In [ ]: