Miért futnak le az osztálydefiníció törzsének utasításai azonnal, azaz mielőtt példányt hoznánk létre belőle?

Az objektumok a logikailag összetartozó adatokat (amelyek az objektumok állapotát jellemzik), valamint az ezeken műveletet végző hívható objektumokat, azaz metódusokat összerendelik. Ezt hívják egységbe zárásnak (encapsulation).

Az azonos típusú objektumoknál a metódusok azonosak, ezért megkülönböztetni egymástól az egyes példányokat az adatattribútumok eltérő értékei alapján lehet. Például a Háromszög típusú objektumok mindegyikére lehet definiálni egy megfelelő képletet tartalmazó kerület és terület számító metódust, valamint az oldalak hosszát képviselő adatattribútumokat. Az egyes megkülönböztethető háromszögobjektumok az oldalhosszakban fognak eltérni.

Ha tehát a metódusok azonosak, akkor – ellentétben az egyediséget meghatározó adatattribútumokkal – felesleges memóriafelhasználás lenne, ha minden egyes objektumhoz külön létrejönnének, és a memóriában permanensen tárolódnának a metódusobjektumok. Ezért ezeket elég egyszer létrehozni, és az egyes objektumpéldányok ezeket a közös hívható objektumokat fogják használni.

Az osztályok definiálásával épp e „közösítést” valósítjuk meg. Az osztály törzsében azokat a függvényeket és adatattribútumokat definiáljuk, amelyeket majd a példányok mindegyike használni fog. Ahhoz azonban, hogy ezek a függvények már használatra készen rendelkezésre álljanak a példányok számára, szükséges, hogy az osztálydefiníció törzsében lefusson a kód, így létrehozva a függvényobjektumokat, amelyek referenciái (nevei) az osztály névterébe bekerülnek.

A példány metódushívásakor az történik, hogy az interpreter rákeres a metódusnévre a példány osztályában. Ha ott megtalálja mint érvényes függvényattribútum, akkor egy hívható objektumot hoz létre, amelynek átadja mind a példányobjektumot, mind az imént megtalált függvényobjektumot. Ezzel hozza létre a metódusobjektumot, amelyet aztán meg is hív olyan módon, hogy a megtalált függvény első argumentumának a példányt adja, utána pedig a híváskor meghatározott többi argumentumot.

Lényeges, hogy szemben az osztályban definiált függvényekkel, a metódusobjektum csak átmenetileg, a hívás idejére jön létre. /Természetesen, ha változóhoz rendeljük, akkor mindaddig létezni fog, amíg van rá hivatkozás./

Mindezeket az alább látható példával szemléltetjük. Itt egy Téglalap1 nevű osztályt definiáltunk, amelyben a két oldal megadásán kívül lehetőség van egy nagyít nevű metódus meghívására, amely egy olyan téglalap példánnyal tér vissza, amelynek oldalai az argumentumban megadott szorosa az eredeti téglalap oldalainak. Az osztálydefiníció utolsó utasítása egy kiírás. A tesztkódok egyszerűek: először létrehozunk egy példányt, majd meghívjuk rá a nagyít metódust, és kiírjuk az eredményt. A programot lefuttatva a várt új téglalapot kapjuk. De előtte még láthatjuk az osztálydefiníció végén szereplő kiírás szövegét. Ez azt jelzi, hogy az osztálydefinícióban szereplő utasítások még a példányosítás előtt végrehajtásra kerülnek.

A következő kódsorokban egy hasonló, téglalap példányokat szolgáltató Téglalap2 osztályt definiáltunk. Itt most a példány metódushívását kívánjuk szimulálni egy saját készítésű Metódus osztály igénybevételével. Ezt úgy érjük el, hogy a Téglalap2 osztály __init__ függvényén belül a példányosításkor létrejövő, self általhivatkozott objektumhoz egy nagyít nevű attribútumot hozunk létre, és ehhez rendeljük a Metódus osztály olyan példányát, amelynek a létrejött téglalap példányt és a Téglalap2 osztály nagyít nevű függvényobjektumát adjuk át. Ahhoz, hogy lássuk, amikor a Metódus példány meghívásra kerül, annak __call__ függvényében egy szöveget írunk ki.

A tesztsorok és azok futási eredményei hasonlóak, mint előzőleg, azzal az eltéréssel, hogy megjelenik az a szöveg is, amely a Metódus objektum hívásakor íródik ki. Ez tehát azt mutatja, hogy jelen esetben nem a normál metódushívás történt a példányon, ahogy az a Téglalap1 esetében, hanem az általunk meghatározott Metódus objektum lett meghívva.

A closure függvények az objektumpéldányokhoz hasonlóan magukhoz csatolják az állapotadatokat (egységbezárják), így akár ezeket is lehetne „példányként” használni. De, ahogy az a több closure függvény azonos körülzárt lokális változóhoz való hozzáféréséről szóló előző bejegyzés végén említésre került, ez csak kevés példány esetén lehet jó megoldás, mert minden egyes closure létrehozásakor az adatattribútumok mellett új hívható objektum is létrejön, ami memóriafelhasználás szempontjából nem kívánatos.

Az osztálypéldányok és metódusok viszonyáról a Python tudásépítés lépésről lépésre című e-könyv „Függvény és objektum szimbiózisa – metódusok” fejezetben, valamint a „Attribútumnév és attribútumérték egymásra találása” szakaszban találhatunk további információkat.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.