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/tulajdonság (attribute)
egy objektum tulajdonsága, 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ó (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 spec. függvény, első argumentuma tipikusan a self.
Öröklődés (inheritance)
származtatással egy már meglévő osztályból egy újat hozunk létre. Ez rendelkezni fog az ős minden tulajdonságával, amihez továbbiakat ad(hat)unk.
Polimorfizmus/többalakúság (polymorphism)
különböző viselkedésmódokkal ruházzuk fel az egymásból származtatott objektumokat.

Ö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-nek.

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ányod 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étel egy
    try:
        ...
    except ... :
        ...
    
    blokkal

Egy Exception-t bármikor el lehet kapni, akármennyi fügvé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

Természetes környezetben. Ha a felhasználó nem egész számot ad, nem tudja azzá konvertálni, így hibát jelez, mi viszont ezt lekezeljük.

In [9]:
while True:
    try:
        x = float(input("Please enter a real number: "))
        break
    except ValueError:
        print("Oops!  That was not a real number.  Try again...")
print(2*x)
Please enter a real number: qwertz
Oops!  That was not a real number.  Try again...
Please enter a real number: fgh
Oops!  That was not a real number.  Try again...
Please enter a real number: 123.123
246.246

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 [10]:
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-10-1b20a8445e65> in <module>
      7 x = "5"
      8 y = g(x)
----> 9 z = f(y)

<ipython-input-10-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 [12]:
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)
123
er
could not convert string to float: 'er'

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 [13]:
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 [14]:
r = range(3)
print(type(r))
it = iter(r)
print(type(it))
<class 'range'>
<class 'range_iterator'>
In [15]:
next(it)
Out[15]:
0
In [16]:
print(next(it))
print(next(it))
1
2
In [17]:
next(it)
-------------------------------------------------------------------------
StopIteration                           Traceback (most recent call last)
<ipython-input-17-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 [18]:
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 kivetelt
        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 [19]:
class Group:
    def __init__(self, name, persons):
        self.persons = persons
        self.name = name
    def __iter__(self):
        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:
    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 [ ]: