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.
Névtér (namespace)
megadja, hogy egy név melyik objektumhoz tartozik (leképezés a nevekről az objektumokra). (Példák névterekre: foglalt nevek, globális változók, egy függvény lokális nevei).
Hatókör/szkóp (scope)
a kód azon része, ahol a névtér közvetlenül (a teljes elérési út magadása nélkül) elérhető.

Öröklődés

Írunk egy Person osztályt. Minden embernek lesz neve és titulusa, a __repr__ 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 [1]:
class Person(object):
    def __init__(self, name, title):
        self.name = name
        self.title = title
    def __repr__(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 epitheton ornansa!

In [2]:
class Person(object):
    def __init__(self, name, title):
        self.name = name
        self.title = title
    def __repr__(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')
launcelot
Out[2]:
Sir Launcelot

Minden lovag megérdemli, hogy a neve mellé az állandó jezőjét is hozzáfűzzük. Ehhez átdefiniáljuk a __repr__ 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(object):
    def __init__(self, name, title):
        self.name = name
        self.title = title
    def __repr__(self):
        return self.title + " " + self.name

class Knight(Person):
    def __init__(self, name, eo):
        super(Knight, self).__init__(name, 'Sir')
        self.eo = eo
    def __repr__(self):
        return self.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

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. Az osztályszinten definiált title azonban egybeesik a Person title változójával így a lovagok esetében a self.title erre a változóra mutat. Ahhoz, hogy az osztályszintű változót elérjük, a Knight.title-ra kell hivatkozni. A searching_for azonban már úgy viselkedik, ahogyan szeretnénk, ez osztályváltozó.

In [4]:
class Person(object):
    def __init__(self, name, title=""):
        self.name = name
        self.title = title
    def __repr__(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 __repr__(self):
        return Knight.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
print launcelot.searching_for
print robin.searching_for
Sir Launcelot, the brave
Sir Robin, the Not-quite-so-brave-as-Sir-Launcelot
The Holy Grail
The Holy Grail
In [5]:
print launcelot.title, "-", Knight.title
print launcelot.searching_for, "-", Knight.searching_for
 - Sir
The Holy Grail - The Holy Grail

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

Iterálható objektumok

Láttuk, hogy a for i in L nem csak akkor működik, ha L lista. 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.

In [6]:
r = range(3)
it = iter(r)
it.next()
Out[6]:
0
In [7]:
print it.next()
print it.next()
1
2
In [8]:
it.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-8-54f0920595b2> in <module>()
----> 1 it.next()

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ó. Az iterálható objekcumok (list, set, tuple) definiálnak egy next() metódust, ami sorban visszaadja az elemeket.

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

class Group(object):
    def __init__(self, name, persons):
        self.persons = persons
        self.name = name
        self.index = -1
    def __iter__(self):
        return self
    def next(self):
        if self.index == len(self.persons) - 1:
            self.index = -1
            raise StopIteration     # dobunk egy kivetelt
        self.index += 1
        return self.persons[self.index]
        
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, különben csak egyszer tudunk iterálni! Persze mindezt megoldhattuk volna egyszerűbben.

In [10]:
class Person(object):
    def __init__(self, name, title=""):
        self.name = name
        self.title = title
    def __repr__(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 __repr__(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

Függvények argumentumai

Lehetőségünk van függvényeket, illetve metódusokat többféle módon meghívni. Ismerjük, hogy hogyan lehet default paraméterértéket megadni.

In [11]:
class A(object):
    def __init__(self):
        pass
    def fuggveny(self):
        print "Eredeti fuggveny"

class B(A):
    def fuggveny(self, i):
        print "B osztaly fuggvenye es", i
        
class C(A):
    def fuggveny(self, i=None):
        if i is None:
            super(C, self).fuggveny()
        else:
            print "C osztaly fuggvenye es", i
        
a = A()
a.fuggveny()

b = B()
b.fuggveny(2)

c1 = C()
c1.fuggveny()

c2 = C()
c2.fuggveny(3)
Eredeti fuggveny
B osztaly fuggvenye es 2
Eredeti fuggveny
C osztaly fuggvenye es 3

Írjunk függvényt, ami felveszi a rendelést! Ha nem említjük, hogy mennyit kérünk, akkor úgy érti, hogy egyet.

In [12]:
def order(name, quantity=1):
    print name, quantity
order('eggs', 2)
order('spam')
eggs 2
spam 1

Most megoldjuk, hogy bármennyi különböző terméket rendelhessünk. Használjuk a *argumentumok formátumú argumentumokat.

In [13]:
def order(name, *args):
    print name
    for a in args:
        print a
order('sausage', 'bacon', 'egg', 'spam')        
sausage
bacon
egg
spam

Ha különböző mennyiségeket akarunk rendelni, akkor másik módszerhez kell folyamodni: használjuk a **argumentumok formátumú argumentumokat.

In [14]:
def order(*args, **kwargs):
    for k in kwargs:
        print k, kwargs[k]
order('egg', 'sausage', spam=4)        
spam 4

Röviden a kivételekről

Vannak olyan esetek, amikor a kód hibába ütközik. Ilyenkor egy Exception típusú objektumot dob a python. Ha senki nem kapja el, akkor a kód megáll futni. 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, de egyben le is kezeljük.

In [15]:
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 [16]:
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 [17]:
while True:
    try:
        x = int(raw_input("Please enter a number: "))
        break
    except ValueError:
        print "Oops!  That was no valid number.  Try again..."
print 2*x
Please enter a number: asdf
Oops!  That was no valid number.  Try again...
Please enter a number: 123b
Oops!  That was no valid number.  Try again...
Please enter a number: 12
24
In [ ]: