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. Leszármaztatni akkor tudunk, ha az ősosztályunk is az object leszármazottja. A leszármazott örökli az ősosztály összes tagváltozóját és metódusát.

In [2]:
class Person(object):
    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(Knight, self).__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 [3]:
class Person(object):
    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(Knight, self).__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 [4]:
class Person(object):
    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(Knight, self).__init__(name, 'Sir')
        self.eo = eo

    def __str__(self):
        if len(self.eo) > 0:
            return self.title + " " + self.name + ", " + self.eo
        else:
            return super(Knight, self).__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 [5]:
class Person(object):
    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(Knight, self).__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 [6]:
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 Exceptiont 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 [7]:
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
<type 'exceptions.Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

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

In [8]:
try:
    str(5) + 5
except TypeError as inst:
    print type(inst)
    print inst.args     
    print inst           
<type 'exceptions.TypeError'>
("cannot concatenate 'str' and 'int' objects",)
cannot concatenate 'str' and 'int' objects

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(raw_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: 3,14
Oops!  That was not a real number.  Try again...
Please enter a real number: 3.14
6.28

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: cannot concatenate 'str' and 'int' objects
In [11]:
def beolvas(n):
    maximum = float("-inf")
    for i in range(n):
        x = float(raw_input())
        if x > maximum:
            maximum = x
    return maximum

try:
    y = beolvas(3)
except ValueError as e:
    print e
1
2
x
could not convert string to float: x

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 [12]:
class KnightException(Exception):
    pass

lancelot = Person("Lancelot", "Mr")
x = str(lancelot)
if x[:3] != "Sir":
    raise KnightException("a", "b")
---------------------------------------------------------------------------
KnightException                           Traceback (most recent call last)
<ipython-input-12-d2e26e0132e9> in <module>()
      5 x = str(lancelot)
      6 if x[:3] != "Sir":
----> 7     raise KnightException("a", "b")

KnightException: ('a', 'b')

Iterálható 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, mely egy iterálható objektumot ad vissza, melyre definiálva van egy next() függvény, 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álható az az objektum, aminek van next metódusa. for ... in után az állhat, aminek van __iter__ metódusa, ami iterálható objektumot ad vissza.

Ezek speciális metódusok!

In [13]:
r = range(3)
it = iter(r)
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álható, ez jelzi az iterálás kezdetét. Az iterálható objekcumok (list, set, tuple) definiálnak egy next() metódust, ami sorban visszaadja az elemeket.

Ha kifogyott, akkor StopIteration kivételt emel.

In [16]:
class Person(object):
    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(Knight, self).__init__(name)
        self.eo = eo
    def __str__(self):
        return Knight.title + " " + self.name + ", " + self.eo    

class Group(object):
    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

Vigyázzunk, hogy az indexet vissza kell állítani az __iter__-ben, különben csak egyszer tudunk iterálni! Persze mindezt megoldhattuk volna egyszerűbben.

In [17]:
class Person(object):
    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(Knight, self).__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):
        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 [ ]: