Subsections


9. Osztályok

A Python osztálymechanizmusa osztályokat ad a nyelvhez a lehető legkevesebb szintaxissal és szemantikával. A Python osztályok a C++ és a Modula-3 -ban található osztálymechanizmusok keveréke. Ahogy a modulokra is igaz, az osztályokban a Python nem határolja el élesen a definíciót és a felhasználót: megbízik a felhasználó ítélőképességében, hogy nem akar a "mindent definiálni fogok" hibájába esni.

Az osztályok legfontosabb tulajdonságai teljes egészében megmaradnak: az öröklődési mechanizmus lehetővé teszi a több őstől való származtatást; a származtatott osztályok a szülők bármely metódusát felül tudják írni; a metódusok ugyanazon a néven érhetik el a szülőosztály metódusait. Az objektumok tetszőleges számú egyéni adatot tartalmazhatnak.

C++ szóhasználattal élve az osztály minden eleme (beleértve az adatokat is) publikus, és minden tagfüggvény virtuális. Nincsenek konstruktor és destruktor függvények. A Modula-3 -hoz hasonlóan nincsen rövidített hivatkozás az objektum alkotóelemeire annak metódusaiból: az objektum függvényeinek deklarálásakor első paraméterként az objektumot jelképező változót adjuk át, melynek értéke értelemszerűen objektumonként változik.

A Smalltalk-hoz hasonlóan az osztályok önmaguk is objektumok, a szó tágabb értelmében, mert a Pythonban minden adattípus objektum.

Ez biztosítja a szó felismerését importáláshoz és átnevezéshez

A C++-tól és a Modula-3 -tól eltérően a beépített típusok szülőosztályokként felhasználhatók. S végül a C++-hoz hasonlóan, de a Modula-3 -tól eltérően a legtöbb, egyéni szintaktikával bíró beépített operátor (aritmetikai műveletek, alprogramok (???: metódusokra gondol?) , stb.) újradefiniálhatók az osztály példányaiban.


9.1 Néhány gondolat a szóhasználatról

Nem lévén általánosan elfogadott szóhasználat az osztályok témakörére, alkalmanként Smalltalk és C++ kifejezéseket fogok használni. (Szerettem volna Modula-3 kifejezéseket alkalmazni, mert annak hasonlít leginkább az objektum-orientáció értelmezése a Pythonhoz, de sajnos nagyon kevesen ismerik ezt a nyelvet.)

Szeretnélek figyelmeztetni a szóhasználatban rejlő csapdára: A Pythonban az objektum szó nem feltétlenül egy osztály példányát jelenti. A C++-hoz és a Modula-3 -hoz hasonlóan, de a Smalltalk-tól eltérően nem minden típus osztály: az alapvető beépített típusok, például az egész számok és a listák (integer, list) nem azok, és néhány eléggé egzotikus típus sem az. (Például a file-ok.) Akárhogy is, minden Python típus osztozik az általános jelölésen, amit a legérthetőbben az objektum kifejezés közelít meg.

Az objektumoknak egyéni jellege van, és többszörösen is el lehet nevezni ugyanazt az objektumot (különböző névterekben). Ez a lehetőség fedőnévként (aliasing) ismert más nyelvekben. Ezt a lehetőséget a nyelvvel való első találkozáskor rendszerint nem becsülik meg - egyébként a fedőnév használata nyugodtan mellőzhető megváltoztathatatlan típusok használatakor (például számok, karakterláncok, tuple-ek esetében).

Ugyanakkor a fedőnév használata szándékosan része a Python nyelvtanának a megváltoztatható típusok esetében, mint például a listák, szótárak - és a legtöbb, programon kívüli entitást jelképező típus esetében (file-ok, ablakok, stb.). A fedőnevek általában a program hasznára válnak, különösen amióta a mutatókhoz hasonlítanak néhány vonatkozásban. Például egy objektum átadása kevés erőforrásfelhasználással jár, mióta csak egy mutató (az objektumra) kerül átadásra a végrehajtás során. Ha a meghívott funkció módosítja a neki átadott objektumot, a hívó látja a változást - ez szükségtelenné teszi két különböző paraméter átadását ( mint például a Pascal-ban). (nincs szükség visszatérési értékre a meghívott függvényből, mert a megváltoztatott objektum közvetlenül elérhető a hívó névteréből. A fordító megjegyzése)


9.2 Hatókörök és névterek a Pythonban

Mielőtt megismerkednénk az osztályokkal, beszélnünk kell a hatókörökről. Az osztálydefiníciók néhány ügyes trükköt adnak elő a névterekkel, és neked ismerned kell a névterek és hatókörök működését ahhoz, hogy teljesen átlásd, mi is történik. Egyébként ennek a témakörnek az ismerete minden haladó Python programozónak a hasznára válik.

Kezdetnek nézzünk meg néhány definíciót.

A névtér a neveket objektumokra képezi le. A legtöbb névtér jelenleg Python szótárakként van megvalósítva, de ez a teljesítmény kivételével normális esetben nem észlelhető - a jövőben ez egyébként változni fog. Példák a névterekre: a foglalt nevek listája (függvények, például az abs(), vagy a beépített kivételek nevei); a modulokban jelenlévő globális nevek; vagy a helyi nevek függvényhívások során. Bizonyos értelemben egy objektum tulajdonságai is külön névteret alkotnak. Fontos tudni, hogy különböző névterekben lévő két név között semmilyen kapcsolat nem létezik. Például ha két különböző modul egyaránt definiálhat egy ``maximum_beallitas'' nevű függvényt bármilyen következmény nélkül, mert a modulok használóinak a függvénynév előtagjában a modul nevével egyértelműen jelezniük kell, hogy pontosan melyik függvényt fogják használni.

Apropó -- a tulajdonság jelzőt használom bármilyen névre, ami a pontot követi -- például a codez.real kifejezésben a real a z objektum egyik tulajdonsága.

Az igazat megvallva egy modulbeli névre való hivatkozás egy tulajdonság-hivatkozás: a codemodname.funcname kifejezésben a modname egy modul objektum és a funcname annak egy tulajdonsága. Ebben az esetben egyenes hozzárendelés történik a modul tulajdonságai és a modulban definiált globális nevek között: ezek egy névtéren osztoznak. 9.1

A tulajdonságok lehetnek csak olvashatóak, vagy írhatóak is. Az utóbbi esetben értéket rendelhetünk a tulajdonsághoz - például így: "modname.the_answer = 42". Az írható tulajdonságok a del utasítással törölhetők is, például a "del modname.the_answer" törli a a modname objektum the_answer tulajdonságát.

A névterek különböző időpontokban születnek, és élettartamuk is változó. Tartalmazzák a Python értelmező beépített neveit, melyek nem törölhetők. A modulok részére a globális névtér a modul definíció olvasásakor jön létre - általános esetben a modul névterek az értelmezőből való kilépésig megmaradnak. Az utasítások az értelmező felső szintű hívásai alapján futnak le, vagy file-ból kiolvasva, vagy az értelmezőbe begépelt utasítások alapján - a __main__ modul megkülönböztetett ???considered??? részeként, ezért saját névtérrel rendelkeznek. (A beépített nevek szintén a modulban léteznek, __builtin__ név alatt.).

A függvények helyi névtere a függvény hívásakor keletkezik, és a függvény lefutásakor, vagy le nem kezelt kivételek létrejöttekor szűnnek meg. (Talán a felejtés szó pontosabb jelző lenne a törlés helyett.) Természetesen a rekurzív hívások mindegyike saját, helyi névtérrel rendelkezik.

A hatókör (scope) a Python kód azon szöveges része ahol a névtér közvetlenül elérhető. A közvetlen elérhetőség itt azt jelenti, hogy a név a teljes elérési útjának kifejtése nélkül elérhető a névtérben. (például a codez.real-ban a . jelzi, hogy a codez objektumhoz tartozó tulajdonságról van szó, ez itt most teljesen kifejtett.)

Ámbár a névterek meghatározása statikus, dinamikusan használjuk őket. Bármikor a program futása során legalább három közvetlenül elérhető névtér létezik: a belső névtér, amiben az értelmező először keres, és helyi neveket valamint függvények neveit tartalmazza (a függvényeket mindig a legközelebbi zárt névtérben keresi az értelmező) -- a másodszor vizsgált középső névtér, ami az aktuális modul globális változóinak neveit tartalmazza -- valamint az utoljára vizsgált külső névtér, ami a beépített neveket tárolja.

Ha egy változónév globálisan deklarált, minden hivatkozás és művelet közvetlenül a középső névtérben keres, mert ott találhatók a modul globális nevei. Fontos tudni, hogy a belső névtéren kívül található nevek csak olvashatóak.

Rendszerint a helyi névtér a szövegkörnyezetben található helyi változókra hivatkozik az aktuális függvényben. A függvényeken kívül a helyi névtér a globális névtérhez hasonlóan egyben az aktuális modul névtere is. Az osztálydefiníciók pedig újabb névtereket helyeznek el a helyi névtérben.

Tudatosítani kell, hogy a névterek a szövegkörnyezet által meghatározottak: a modulban definiált függvény globális névtere a modul névterében jön létre - a függvény nevei kizárólag itt elérhetők.

Másrészről az aktuális nevek keresése még dinamikusan, futásidőben történik, - a nyelvi definíció akármennyire is törekszik a fordításkori, statikus névfeloldásra, szóval hosszútávon ne számíts a dinamikus névfeloldásra! (Igazság szerint a helyi változók már statikusan meghatározottak)

A Python egy különleges tulajdonsága, hogy a hozzárendelés mindig a belső névtérben történik. A hozzárendelés nem másol adatokat -- csak kötést hoz létre a nevek és az objektumok között. A törlésre ugyanez igaz: a "del x" utasítás eltávolítja az x kötését a helyi névtér nyilvántartásából,

Valójában minden művelet, ami új név használatát vezeti be, a helyi névteret használja - például utasítások importálása, és függvénydefiníciók modulbeli létrehozása vagy kötése.

A global kulcsszóval jelezheted hogy bizonyos változók a globális névtérben léteznek.


9.3 Első találkozás az osztályokkal

Az osztályok használatához szükségünk van valamennyi új nyelvtanra, három új objektumtípusra, és néhány új kifejezés használatára.


9.3.1 Az osztálydefiníció szinaxisa

A legegyszerűbb osztálydefiníció így néz ki:

class Osztalynev:
    <utasitas-1>
    .
    .
    .
    <utasitas-N>

Az osztálydefiníciók hasonlítanak a függvények definíciójára (def statements) abból a szempontból, hogy az osztály deklarációjának meg kell előznie az első használatot. Osztálydefiníciót elhelyzehetsz egy if utasítás valamely ágában is, vagy egy függvénybe beágyazva.

A gyakorlatban az osztályokon belüli utasítások többsége általában függvénydefiníció, de bármilyen más utasítás is megengedett, és néha hasznos is -- erre még később visszatérünk. Az osztályon belüli függvényeknek egyedi argumentumlistájuk (és hívási módjuk) van az osztály metódusainak hívási módja miatt -- ezt szintén később fogjuk megvizsgálni.

Egy osztálydefinícióba való belépéskor új névtér jön létre és helyi névtérré válik -- ebből kifolyólag minden helyi változóra történő hivatkozás átkerül ebbe az új névtére. ???A gyakorlatban általában az új függvénydefiníciók kerülnek ide.???

Az osztálydefiníciókból való normális kilépéskor egy class object objektum jön lére. Ez lényegében egybefoglalja, beburkolja az osztálydefiníciókor létrejött új névtér tartalmát - az osztályobjektumokról a következő alfejezetben fogunk többet tanulni. Az eredeti helyi névtér (az osztálydefinícióba való belépés előtti állapotában) helyreállítódik, és az osztályobjektum neve is a helyi névtér része lesz (az Osztalynev a példában).


9.3.2 Osztályobjektumok

Az osztályobjektumok a műveletek kétféle típusát támogatják: attribútum műveletek és a példányosítás. Az Attribútum tulajdonságok az általánosan használt Python jelölésmódot használják: objektum.attribútumnév. Az összes név érvényes attribútumnév ami az osztály névterében volt az osztályobjektum létrehozásakor. Ha egy osztály definíció valahogy így néz ki:

class Osztalyom:
    "Egy egyszeru pelda osztaly"
    i = 12345
    def f(self):
        return 'hello vilag'

akkor codeOsztalyom.i és Osztályom.f egyaránt érvényes attribútum hivatkozás - egy egész számmal és egy objektum-eljárással térnek vissza. Az osztályattribútumoknak ugyanúgy adhatunk értéket mint egy normális változónak (Osztalyom.i = 2). A __doc__ eljárás is érvényes attribútum, ami az osztály definíciójában lévő első szabad, nem hozzárendelt string-objektummal tér vissza: "Egy egyszeru pelda osztaly"

Class instantiation uses function notation. Just pretend that the class object is a parameterless function that returns a new instance of the class. For example (assuming the above class):

Egy osztály példányosítása a függvények jelölésmódjához hasonló. Egyszerűen úgy kell tenni, mintha az osztályobjektum egy paraméter nélküli függvény lenne, amit meghívva az osztály egy új példányát kapjuk visszatérési értékként. Például (a fenti osztályt alapul véve):

x = Osztalyom()

creates a new instance of the class and assigns this object to the local variable x.

készít egy új példányt az osztályból, és hozzárendeli a visszatérési értékként kapott objektumot az x helyi változóhoz.

//::::::::::::::::::::::::::::::::::::::::::::::::::::::: // 2004. okt. 7:

A példányosítás művelete (az objektum ``hívása'') egy üres objektumot hoz létre. Elképzelhető, hogy az új példányt egy ismert kezdeti állapotba állítva szeretnénk létrehozni. Ezt egy különleges metódussal, az __init__()-el tudjuk elérni:

    def __init__(self):
        self.data = []

Ha az osztály definiálja az __init__() metódust, egy új egyed létrehozásakor annak __init__() metódusa automatikusan lefut. Lássunk egy példát egy új, inicializált egyedre:

x = Osztalyom()

Természetesen az __init__() metódusnak paramétereket is átadhatunk. A paraméterek az osztály példányosítása során az inicializáló metódushoz jutnak. Lássunk egy példát:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
... 
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)


9.3.3 A létrehozott egyedek

És most mihez tudunk kezdeni az új egyedekkel? Fontos hogy tisztában legyünk az új objektumok lehetséges alkotóelemeivel. Két lehetséges alkotóelem van: a belső változók (adat tulajdonságok) és az ezekkel dolgozó függvények.

A Pythonban használt adat tulajdonságok fogalma megegyezik a Smalltalk ``példány változó'' fogalmával, és a C++``adat tagok'' fogalmával. Az adat tulajdonságokat nem kell a használatuk előtt deklarálni, a helyi változókhoz hasonlatosan működnek - az első használatukkor automatikusan létrejönnek. Például ha x az Osztalyom egy példánya, a következő kódrészlet 16-ot fog kiírni: ("anélkül, hogy letérnénk az útról", Ford. javítani!)

x.szamlalo = 1
while x.szamlalo < 10:
    x.szamlalo = x.szamlalo * 2
print x.szamlalo
del x.szamlalo

Fentebb már említettük, hogy az objektumnak lehetnek saját függvényei (más néven metódusai) is. Fontos hogy ezek működését is megértsük. A metódus pontosan egy objektumhoz tartozó függvényt jelöl. (A Pythonban a metódus kifejezés nem kizárólag egy osztály példányát jelenti - más objektum típusok is rendelkezhetnek metódusokkal. Például a lista objektumoknak vannak saját metódusai: append, insert, remove, sort, és így tovább. A ford. megjegyzése: valójában ezek is egy - a nyelvbe beépített osztály példányai, csak tudatosítani kell, hogy itt a Python helyettünk elvégzi a példányosítást, és mi ennek a példánynak a metódusait használjuk. Az alábbi sorokban a metódus kifejezést kizárólag egy osztály metódusaira értjük, hacsak nincs külön kihangsúlyozva, hogy most egy másik objektum metódusáról van szó.

A létrehozott objektum metódusainak neve a szülőosztálytól függ. (Egy osztály belső elemei lehetnek: függvények vagy változók) Meghatározás szerint minden felhasználó által definiált osztályfüggvényt az adott (létező) példány nevével kell hívni. Például x.f egy érvényes függvényhivatkozás, ha az Osztalyom.f függvény létezik (x objektum az Osztalyom példánya), de x.i nem érvényes ha Osztalyom.i változót nem hoztuk létre az osztály definiálásakor. Fontos, hogy x.f nem ugyanaz, mint Osztalyom.f -- ez egy Objektum metódus, nem egy függvény objektum. Fordító megjegyzése: az osztálydefiníció egy minta, ami alapján a konkrét objektumokat létrehozza a Python. Van egy prototípus, ami alapján legyártható több példány - a prototípus nyilván nem ugyanaz, mint a róla mintázott újabb egyedek. Más nyelvekben jártas programozóknak zavaró lehet hogy a tutorial a függvényeket ugyanúgy jelöli, mint a változókat: x.f egy függvény, míg például a php-ban ezt a tényt az üres zárójelekkel külön jelölik: x.f()


9.3.4 Az objektum metódusok

Többnyire a metódusokat közvetlenül használjuk:

x.f()

//::::::::::::::::::::::::::::::::::::::::::::::::::::::: // 2004. okt. 9:

Példánkban x.f a 'hello vilag' string-el tér vissza. Ezt a függfényt nem csak közvetlenül hívhatjuk meg: ?? x.f egy objektum metódus, tárolható és később is hívható, például így:

xf = x.f
while True:
    print xf()

Ez a kód az örökkévalóságig a "hello vilag" üzenetet írja ki.

Pontosan mi történik egy objektummetódus hívásakor? Lehet, hogy már észrevetted hogy a x.f()-t a fenti példában paraméterek nélkül hívtuk meg - annak ellenére, hogy f függvénydefiníciója egy paraméter használatát előírja. Mi van ezzel a paraméterrel? Szerencsére a Pythonban ha egy paramétert igénylő függvényt paraméter nélkül próbálunk használni, kivételdobás történik.

Lehet hogy már kitaláltad a választ: az a különleges a metódusokban, hogy hívásukkor az őket tartalmazó osztálypéldányt megkapják az első változóban. A példánkban x.f() hívása pontosan ugyanaz, mintha Osztalyom.f(x) metódust hívnánk. Ford. megjegyzése: valójában a függvénynek itt is jelezzük, hogy az x egyedről van szó, a paraméter miatt. Általában metódusok hívása n paraméterrel ugyanaz, mintha az osztálydefiníció függvényét hívnánk meg úgy, hogy a legelső paraméter elé az aktuális példány nevét beillesztjük.

Ha nem értenél valamit a metódusok működéséről, nézz meg kérlek néhány gyakorlati példát. Amikor egy példány tulajdonságára hivatkozol, és az nem létezik a változók között, az értelmező az osztálydefinícióban fogja keresni. Ha a név egy érvényes osztálytulajdonságra mutat, ami egy fügvény, a fenti példában szereplő folyamat történik: az értelmező az x.f() hívást átalakítja - a paramétereket kigyűjti, majd első paraméterként x-et tartalmazva létrehoz egy új paraméterlistát és meghívja a Osztalyom.f(x, paraméter1, p2...) függvényt. Az adott példányban tehát a függvény valójában nem is létezik (absztrakt objektum).

Az adat attribútumok felülírják az ugyanolyan nevű metódusokat; a névütközések elkerülése végett (amelyek nagyon nehezen megtalálható programhibákhoz vezethetnek) érdemes betartani néhány elnevezési szabályt, melyekkel minimalizálható az ütközések esélye. Ezek a szabályok például a metódusok nagybetűvel írását, az adat attribútumok kisbetűs írását - vagy alsóvonás karakterrel történő írását jelentik; vagy igék használatát a metódusokhoz, és főnevekét az adat attribútumokhoz.

Az adat attribútumokra a metódusok is hivatkozhatnak, éppúgy mint az objektum hagyományos felhasználói. Más szavakkal az osztályok nem használhatók csupasz absztrakt adattípusok megvalósítására. Valójában a Pythonban jelenleg semmi sincs, ami az adatrejtés elvét biztosítani tudná - minden az elnevezési konvenciókra épül. Másrészről az eredeti C alapú Python képes teljesen elrejteni a megvalósítási részleteket és ellenőrizni az objetkum elérését, ha szükséges; ehhez egy C nyelven írt kiegészítést kell használni.

A kliensek az adat-atribútumokat csak óvatosan használhatják, mert elronthatják azokat a variánsokat, amelyeket olyan eljárások tartanak karban, amelyek időpont-pecsételéssel dolgoznak. Az objektum felhasználói saját adat attribútumaikat bármiféle ellenőrzés nélkül hozzáadhatják az objektumokhoz amíg ezzel nem okoznak névütközést -- az elnevezési konvenciók használatával elég sok fejfájástól megszabadulhatunk!

A metódusokon belül nem létezik rövidítés, gyors hivatkozás az adat attribútumokra. Én úgy látom, hogy ez növeli a metódusok olvashatóságát, és nem hagy esélyt a helyi és a példányosított változók összekeverésére, mikor a metódus forráskódját olvassuk.

A hagyományokhoz hűen a metódusok első paraméterének neve rendszerint self. Ez valóban csak egy szokás: a self névnek semmilyen speciális jelentése nincs a Pythonban. (Azért vegyük figyelembe, hogy ha eltérünk a hagyományoktól, akkor a program nehezebben olvashatóvá válik, és a class browser - osztály böngésző is a tradicionális változónevet használja.

Az osztály definíciójában megadott függvények az osztály példányai számára hoznak létre metódusokat (a példányhoz tartozó függvényeket). Nem szükségszerű azonban hogy egy függvénydefiníció kódja az osztálydefiníció része legyen: egy definíción kívüli függvény helyi változóhoz való rendelése is megfelel a célnak. Például:

% # Function defined outside the class
# Egy osztályon kívül definiált függvény
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'hello vilag'
    h = g

Most f, g és h egyaránt C osztály attribútumai (gyakorlatilag objektum hivatkozások) -- következésképpen C osztály minden példányának metódusai is-- h és g pedig valójában ugyanazt a függvényt jelentik. Azért ne feledjük, hogy a fenti példa használata a program olvasóját összekavarhatja!

Az osztályon belüli metódusok egymást is hívhatják a self argumentum használatával:

class Taska:
    def __init__(self):
        self.tartalom = []
    def belerak(self, x):
        self.tartalom.append(x)
    def belerak_ketszer(self, x):
        self.belerak(x)
        self.belerak(x)

A metódusok a globális névtérben lévő függvényekre is hasonlóképp hivatkozhatnak. (Maguk az osztálydefiníciók soha nem részei a globális névtérnek!) Míg egy kivételes esetben a globális névtér változóinak használata jól jöhet, több esetben is jól jöhet a globális névtér elérése: a globális névtérbe importált függvényeket és modulokat az adott osztálymetódusból is használhatjuk, mintha az adott függvényben vagy osztályban definiálták volna őket. Rendszerint az osztály az önmaga által definiált metódust a globális névtérben tartja, és a következő részben meglátjuk majd, miért jó ha a metódusok a saját osztályukra hivatkozhatnak!


9.4 Öröklés

Természetesen az öröklés támogatása nélkül nem sok értelme lenne az osztályok használatának. A származtatott osztályok definíciója a következőképpen néz ki:

class SzarmaztatottOsztalyNeve(SzuloOsztalyNeve):
    <utasitas-1>
    .
    .
    .
    <utasitas-N>

a SzuloOsztalyNeve névnek a származtatott osztály névterében léteznie kell. Abban az esetben, ha a szülőosztály másik modulban lett definiálva, a modulnev.szuloosztalynev formát is használhatjuk:

% class DerivedClassName(modname.BaseClassName):
class SzarmaztatottOsztalyNeve(modulnev.SzuloOsztalyNeve):

A származtatott osztály definíciójának feldolgozása hasonló a szülőosztályokéhoz -- az osztályobjektum létrehozásakor a szülőosztály is a példány része lesz. Ha egy osztály-attribútumra hivatkozunk, és az nincs jelen az osztályban, az értelmező a szülőosztályban keresi azt -- és ha az szintén származtatott osztály, akkor annak a szülőjében folytatódik a keresés, rekurzívan.

A származtatott osztályok példányosításában nincs semmi különleges: SzarmaztatottOsztalyNeve() létrehozza az osztály új példányát. A metódusok nyilvántartását a következőképp oldja meg az értelmező: először az aktuális osztály megfelelő nevű osztály-objektumait vizsgálja meg, majd ha nem találja a keresett metódust, elindul a szülőosztályok láncán. Ha a keresési folyamat eredményes, tehát valamelyik szülőosztályban megtalálta a keresett nevű objektumot, az adott objektumnév hivatkozása érvényes lesz: a talát objektumra mutat.

A származtatott osztályok felülírhatják a szülőosztályok metódusait. A metódusoknak nincsenek különleges jogaik más, ugyanabban az objektumban lévő metódusok hívásakor. A szülőosztály egy metódusa /a_eredeti()/, amely ugyanazon szülőosztály egy másik metódusát hívja /b(), lehet hogy nem fog működni, ha a származtatott osztályból felülírják /a_uj()/, ami nem hívja már b()-t. (C++ programozóknak: a Pythonban minden metódus valójában virtual típusú.

A származtatott osztály metódusa, amely felülírja a szülőosztály egy metódusát, valójában inkább kiterjeszti az eredeti metódust, és nem egyszerűen csak kicseréli. A szülőosztály metódusára így hivatkozhatunk: "SzuloOsztalyNev.metodusnev(self, paraméterek)". Ez néha jól jöhet. (Fontos, hogy ez csak akkor működik, ha a szülőosztáy a globális névtérben lett létrehozva, vagy közvetlenül beimportálva.)


9.4.1 Többszörös öröklés

A python korlátozottan támogatja a többszörös öröklést is. Egy ilyen osztály definíciója a következőképp néz ki:

% class DerivedClassName(Base1, Base2, Base3):
%     <statement-1>
class SzarmaztatottOsztalyNeve(Szulo1, Szulo2, Szulo3):
      <utasitas-1>

    .
    .
    .
%     <statement-N>
      <utasitas-N>

Az egyedüli nyelvtani szabály amit ismerni kell, az osztály attribútumok feloldásának a szabálya. Az értelmező először a mélyebb rétegekben keres, balról jobbra. A fenti példában ha az attribútum nem található meg a SzarmaztatottOsztaly-ban, akkor először a Szulo1-ben keresi azt, majd rekurzívan a Szulo2-ben, és ha ott nem találja, akkor lép tovább rekurzívan a többi szülőosztály felé.

Néhányan első pillanatban arra gondolnak, hogy a Szulo2 -ben és a Szulo3-ban kellene előbb keresni, a Szulo1 előtt -- mondván hogy ez természetesebb lenne. Ez az elgondolás viszont igényli annak ismeretét, hogy mely attribútumot definiáltak a Szulo1-ben vagy annak egyik szülőosztályában, és csak ezután tudod elkerülni a Szulo2-ben lévő névütközéseket. A mélyebb először szabály nem tesz különbséget a helyben definiált és öröklött változók között.

Ezekből gondolom már látszik, hogy az átgondolatlanul használt többszörös öröklődés a program karbantartását rémálommá teheti - a névütközések elkerülése végett pedig a Python csak a konvenciókra támaszkodhat. A többszörös öröklés egyik jól ismert problémája ha a gyermekosztály két szülőosztályának egy közös nagyszülő osztálya van. Ugyan egyszerű kitalálni hogy mi történik ebben az esetben (a nagyszülő adat attribútumainak egypéldányos változatát használja a gyermek) - az még nem tisztázott, hogy ez a nyelvi kifejezésmód minden esetben használható-e.


9.5 Private Variables


9.6 Egyéni változók

Egyedi azonosítók létrehozását az osztályokhoz a Python korlátozottan támogatja. Bármely azonosító, amely így néz ki: __spam (legalább két bevezető alsóvonás, amit legfeljebb egy alsóvonás követhet) ??? ez bizonytalan szövegesen kicserélődik a _classname__spam formára, ahol a classname az aktuális osztály neve egy alsóvonással bevezetve. Ez a csere végrehajtódik az azonosító pozíciójára való tekintet nélkül, úgyhogy használható osztály-egyedi példányok, osztályváltozók, metódusok definiálására -- még akkor is, ha más osztályok páldányait saját privát változói közé veszi fel (Ford: ellenőrizni!)

Ha a cserélt név hosszabb mint 255 karakter, az értelmező csonkíthatja az új nevet. Külső osztályoknál, vagy ahol az osztálynév következetesen alsóvonásokból áll??? nem történik csonkítás.

A névcsonkítás célja az, hogy az osztályoknak egyszerű megoldást biztosítson a ``private'' változók és metódusok definiálására -- anélkül, hogy aggódnunk kellene a származtatott osztályokban megjelenő privát változók miatt, vagy esetleg a származtatott osztályban már meglévő változó privát változóval való felülírása miatt. Fontos tudni, hogy a névcsonkítási szabályok elsősorban a problémák elkerülését célozzák meg -- így még mindig leheséges annak, aki nagyon akarja, hogy elérje vagy módosítsa a privátnak tartott változókat.

Ez speciális körülmények között nagyon hasznos lehet, például hibakeresésnél, és ez az egyik oka annak, hogy ezt a kibúvót még nem szüntették meg.

(Buglet (duda): származtatás egy osztályból a szülőosztály nevével megegyező néven - a szülőosztály privát változóinak használata ekkor lehetséges lesz.)

Fontos, hogy a exec, eval() vagy evalfile() által végrehajtott kód nem veszi figyelembe a hívó osztály nevét az aktuális osztály esetében -- ez hasonló a global változók működéséhez, előre lefordított byte-kód esetében. Hasonló korlátozások léteznek a getattr(), setattr() és delattr() esetében, ha közvetlenül hívják meg a __dict__ utasítást.


9.7 Egyebek...

Alkalomadtán hasznos lehet a Pascal ``record'', vagy a C ``struct'' adattípusaihoz hasonló szerkezetek használata - egybefogni néhány összetartozó adatot. A következő üres osztálydefiníció ezt szépen megvalósítja:

% class Employee:
class Alkalmazott:
    pass

% john = Employee() # Create an empty employee record
john = Alkalmazott() # Egy üres alkalmazott rekordot hoz létre

% # Fill the fields of the record
% john.name = 'John Doe'
% john.dept = 'computer lab'
% john.salary = 1000
# A rekord mezőinek feltöltése
john.nev = 'John Doe'
john.osztaly = 'számítógépes labor'
john.fizetes = 1000

Ez a kis kódrészlet ami egyéni adat típusokat kezel, gyakran követ adatszerkezetek tárolására való osztálydefiníciókat.

Az egyes objektumpéldányok saját tulajdonságokkal is rendelkeznek: m.im_self az aktuális objektumpéldányra mutat ??? ezt ki kell próbálni???, m.im_func az objektumban elérhető metódusokat tartalmazza. ??? ezt is próbáld ki


9.8 Kivételek alkalmazása az osztályokban

A felhasználói kivételek az osztályokban is működnek -- használatukkal egy bővíthető, hierarchikus kivétel-struktúrát építhetünk fel.

A raise utasításnak kétféle használati módja lehetséges:

raise Osztaly, peldany

raise peldany

Az első esetben peldany-nak az Osztaly-ból kell származnia. A második eset egy rövidítés:

% raise instance.__class__, instance
raise peldany.__class__, peldany

Az except záradékban lévő class megegyezik egy kifejezéssel ha az ugyanaz az osztály vagy a szülőosztály (de nem másik kerülő úton -- az except záradékban lévő származtatott osztály figyelése nem egyezik meg a szülőosztály figyelésével.) Például a következő kód B, C, D kimenetet fog produkálni:

class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B, C, D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B"

Note that if the except clauses were reversed (with "except B" first), it would have printed B, B, B -- the first matching except clause is triggered.

Fontos, hogy ha az except záradékot megfordítjuk ("except B" van először), B, B, B lesz a kimenet -- az első except-re való illeszkedés után a többit már nem vizsgálja az értelmező.

Mikor egy kezeletlen kivétel miatt hibaüzenetet küld az értelmező, és a hiba egy osztályból származik, a kimenetben szerepel az osztály neve, egy kettőspont, egy space, és befejezésül a példányt stringgé konvertálja az értelmező a beépített str() függvénnyel.


9.9 Bejárók

Valószínűleg már észrevetted, hogy a legtöbb tároló objektum bejárható a for ciklusutasítás használatával:

for element in [1, 2, 3]:
    print element
for element in (1, 2, 3):
    print element
for key in {'one':1, 'two':2}:
    print key
for char in "123":
    print char
for line in open("myfile.txt"):
    print line

A ciklus használatának módja tiszta, tömör és kényelmes. A ciklusok a Python egyik nyelvi alappillérét alkotják. A színfalak mögött a for utasítás meghívja a iter() függvényt, amely a bejárandó objektumhoz tartozik. Ez a függvény egy ciklus objektummal tér vissza, ami meghatározza a next() metódust, amellyel az objektum elemeit lehet elérni. Ha a függvény eléri az objektum utolsó elemét, és a következő elemre akarunk lépni, StopIteration kivételt okozunk, amely a for ciklust megszakítja. És lássuk mindezt a gyakorlatban:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in -toplevel-
    it.next()
StopIteration

A ciklusok működésébe bepillantva már könnyű saját bejáró eseményt készíteni egy osztályhoz. Definiálni kell az __iter__() metódust, ami a next() függvény eredményeképp létrejövő objektummal tér vissza. Ha az osztály definiálja a next() metódust, akkor az __iter__() egyszerűen a self objektummal tér vissza:

>>> class Reverse:
    "Iterator for looping over a sequence backwards"
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> for char in Reverse('spam'):
	print char

m
a
p
s


9.10 Generátorok

A generátorok egyszerű és hatékony eszközök (adat)bejárók készítésére. A normális függvényekhez hasonló a felépítésük, de használják a yield utasítást ha adatot szeretnének visszaadni. A next() metódus minden hívásakor a generátor ott folytatja az adatok feldolgozását, ahol az előző hívásakor befejezte (emlékszik minden változó értékére, és hogy melyik utasítást hajtotta végre utoljára). Lássunk egy példát arra, hogy milyen egyszerű egy generátort készíteni:

>>> def reverse(data):
        for index in range(len(data)-1, -1, -1):
            yield data[index]
		
>>> for char in reverse('golf'):
        print char

f
l
o
g

Bármi, amit egy generátorral megtehetsz, osztály alapú bejárókkal is kivitelezhető - az előző részben leírtak szerint. Ami a generátorokat teljessé??? teszi, hogy a __iter__() és a next() metódusok automatikusan létrejönnek.

Egy másik fontos lehetőség hogy a helyi változók, és a végrehajtás állapota automatikusan tárolódik a generátor két futása között.

Az automatikus metódus generálás és program állapot mentés gondolatmenetét folytatva, amikor a generátor futása megszakad, ez az esemény automatikusan StopIteration kivételt vált ki. Egymással kombinálva ezek a nyelvi szolgáltatások egyszerűvé teszik a bejárók készítését néhány függvény megírásának megfelelő - viszonylag kis erőfeszítéssel.



Footnotes

... osztoznak.9.1
Kivéve egy esetet: a modul objektumoknak van egy rejtett csak olvasható tulajdonságuk, a __dict__ amit kiolvasva megkapjuk a modul névterében lévő nevek listáját egy szótár típusú változóban. A __dict__ egy tulajdonság, de nem egy globális név. Használata nyilvánvalóan megsérti a névtér-elv alkalmazásának tisztaságát, és csak olyan végső esetekben alkalmazható, mint a program-kiakadások után futtatott hibakeresők.
See About this document... for information on suggesting changes.