Metaprogramozás I
Bevezető
A metaclassok nyitják meg a teret a metaprogramozás előtt kedvelt nyelvünkben. Megértésükhöz szükséges néhány fogalmat és építőelemet tisztázni, mert nem a metaclassok a nehezek magukban, hanem az a több tucat pythonos építőkocka sok, amit össze kell gyűjteni. RePa dekorátorjaihoz és descriptorjaihoz hasonlóan semmi olyat nem tesznek lehetővé amit vagy a megszokott procedurális vagy objektum orientált eszközeinkkel ne tudnánk kifejezni vagy megoldani. Főképp APIk létrehozását teszik áttekinhetővé (pl Django ORM és newforms API). Természetesen Djangóban mindig ott figyelnek a háttérből és ha valaki már dolgozott adatbázis modellekkel/formokkal, akkor használta őket akarva, akaratlanul.
Minden ojjektum és type
Úgy marketingelik a Python nyelvet, hogy minden objektum benne, és 100%-ig ojjektum orientált nyelv, tehát pl. nincsenek benne primitívek, mint Javában. :) Ne higgyünk el mindent amit olvasunk, járjunk utána. Objektum-e pl egy tetszőleges python modul? Bármilyen "névnek" lekérdezhetjük a típusát a type beépített fügvénnyel. Név alatt értek mindent ami az adott névtérben elérhető :). Ezek elég szerteágazóak: változók, függvények, modulok, osztályok, objektum példányok és mindenki aki lemaradt.
>>> import sys >>> print type(sys) <type 'module'> >>> print sys.__class__ <type 'module'> >>> print type(3) <type 'int'> >>> print type(u"123") <type 'unicode'> >>> print type(lambda x:x+1) <type 'function'>
Azaz minden modul objektum, típusuk (osztályuk) module. Nézzük mi a module típusa.
>>> print type(type(sys)) <type 'type'>
Tehát a típusa type. Ez minden típus őse. A type azonban nem csak típusok lekérdezésére szolgál, hanem létrehozására is. Ha egy objektumot adunk neki paraméterként, a típusát adja vissza, amit rögtön példányosíthatunk, azaz létrehozhatunk belőle egy objektumot.
>>> mystring = type("string")() >>> print mystring.__class__ <type 'str'> >>> print len(mystring) 0
Azaz létrehoztunk egy üres stringet, úgy hogy egy meglévő string típusát megkaptuk és meghívtuk mint függvényt, azaz a típus konstruktorát használtuk. A visszakapott érték egy üres string.
A függvényhívás operátor pythonban
Álljunk meg egy szóra itt, érdemes megjegyezni hogy pythonban nincs new operátor, amivel osztályokat példányosíthatnánk. Az osztály meghívjuk mint egy függvényt, ezzel hozunk létre új élő objektumot. A python nyelv igazából csak a callable, azaz meghívható fogalmat ismeri.
Meghívhatunk egy függvényt, lefut, esetleg visszatérési értéket is ad.
Meghívhatunk egy osztályt, ezzel példányosítunk, létrehozunk egy objektumot, ez az objektum a függvény visszatérési értéke.
Meghívhatunk egy példányt is, ekkor a legtöbb esetben hibát kapunk, hacsak nem tesszük callable-é a példányt, vagy osztályát. Tegyük:
Érezzük át a pythont a szabályok mögött, az hogy valami meghívható ismét egy protokoll, amely a zárójelek használatán, a paraméterek átadásán és a visszatérési értéken alapszik.
class CallableKlassz(object): def __call__(self,*args): " Csak ennyit kell tenned ahhoz hogy meghívható lehess. " print 'From now on, you can call me callable!' print ",".join(args) >>> k = CallableKlassz() >>> k('1','2','3') From now on, you can call me callable! 1,2,3 # a callable() függvény megmondja hogy egy adott név meghívható vagy sem # k biztosan callable, ezért nem keletkezik hiba. assert callable(k)
Van az úton egy buktató mielőtt tovább mehetnénk, ami a 2.2es python körül történt. Ezekről most muszáj szót említeni, hogy senki ne essen a csapdájukba.
Régi vs. Új osztályok
A 2.2es python egyesítette a típus rendszerét, így feloldottak egy ellentmondást, és létrehozták az új stílusú osztályokat. A régi stílusúak kompatibilitási okokból a 2.X verziókban működnek, 3-masban már tilos, többek között ezért sem fut még Django a 3-mas vonalon.
# Régi típusú osztály class RegiKlassz: pass # Régi típusú osztály ismét class RegiKlassz(): pass # Új típusú osztály class UjKlassz(object): pass # ez is új típusú class UjKlassz2(Shape,Rectange,PrintAbleRectangle): pass
Tehát szintatikailag csak annyi a különbség hogy explicit megmondjuk, hogy van ősünk. Aki csak teheti kerülje a régi írásmódot, mellesleg pedig nem is működik az itt leírt csodák jelentős része régi típusú osztályokkal. Csak ennyit jegyezzünk meg: explicit módon mindig jelezzük hogy kitől vagy kiktől öröklünk.
Tehát új osztályokra építve nézzük tovább a típusrendszert.
A type függvény
A kitérő után vissza a type használatára. A type egy beépített (builtin) függvény, amivel futási időben létrehozhatunk egy típust (mondhatjuk egy osztályt). Ha létrehoztuk a típust, akkor már példányosíthatjuk is. Három paramétere van:
type(name, bases, dict)
A name az új osztályunk neve, természetesen string. (2.X verzióban csak csak bytestring, nem lehet unicode, de ne is akard. :) )
A bases egy zárt lista (tuple), ami az ősöket listázza, lévén hogy pythonban engedett a többszörös öröklés.
Végül dict egy dictionary, ami a létrejövő osztályunk tagjait tartalmazza, mind a mezőket és metódusokat.
Szereljünk össze egy összetettebb osztályt, hogy lássuk milyen egyszerű is ez. Származtassunk egy osztály a lista típusból (list), legyen saját konstruktora, egy metódusa és egy változója, ami egy jól ismert konstans lesz jelen esetben.
def method1(self): print 'method1()' def konstruktor(self): print "new instance created()" self.foo = "bar" my_dynamic_made_class = type('MyDynamicMadeClass', (list, object), {'PI':3.14, 'method1':method1, '__init__':konstruktor}) # Osztálya: # <class '__main__.MyDynamicMadeClass'> print my_dynamic_made_class # Azért működik mindkettő mert PI elérhető # mind az osztályon, mind a példányon keresztül. # Ez a __getattr__ metódus bűne :) print my_dynamic_made_class.PI print my_dynamic_made_class().PI # Példány metódus működik my_dynamic_made_class().method1() # Osztályon keresztül nem hívhatjuk, mert a statikus és # osztály metódusok már másképp működnek mint a példány metódusok. # El tudjuk érni, azaz látjuk, csak nem úgy működik ahogy szeretnénk. try: my_dynamic_made_class.method1() except TypeError: # TypeError: unbound method method1() must be called with # MyDynamicMadeClass instance as first argument (got nothing instead) pass peldany = my_dynamic_made_class() # kíirja a "bar" szót print peldany.foo # és mivel listából származtattuk ez is megy peldany += [1,2,3] # és iterálható is, mert listából származtattuk... for i in peldany: print i
Ezt az osztály dinamikusan szereltük össze. Azaz nincs deklarálva, mondhatnánk nincs statikus definíciója. Tegyük meg ugyanezt a régi, jól bevált statikus módon:
class MyOldClass(list,object): PI = 3.14 def __init__(self): print "new instance created()" self.foo = "bar" def method1(self): print 'method1()'
Látszik hogy az utóbbi jóval tömörebb, érthetőbb és szebb is. A type függvénnyel tehát két dolgot is tehetünk:
megkaphatjuk egy tetszőleges név típusát válaszul
létrehozhatunk új típust
A kapott értékek típus referenciák. Ezeket továbbpasszolhatjuk függvényeknek, vagy példányokat gyárthatunk belőle.
Látjuk azt is, ha a fordított irányból nézzük, hogy miből is áll egy python osztály.
egy név, illik nagybetűvel (hogy PEP8 kompatibilis maradj) kezdeni az összes szavát (EzEgySzepNevuOsztaly)
az ős osztályai, melyek futási időben is bejárhatóak ( OsztalyNeve.__bases__ lista, vagy OsztalyNeve.__base__ az elsőt adja)
egy szótár (dictionary) amely az összes elemét (mezők és metódusok) tartalmazza, ez a __dict__.
Még egy elemre lesz szükség, hogy a metaclassok könnyen elmagyarázzák magukat ezek pedig a mágikus mezők.
Magic Methods és Magic Fields
Aki látott már PHP-ben OOP-t az akár át is ugorhatja ezt a kitérőt. A mágikus nevek úgy működnek, hogy bár nem rezervált kulcsszavak, azonban ha osztályon belül használod őket speciális módon fogja őket meghívni vagy kezelni a python futtató. Tehát osztályon belül igenis foglalt a nevük, de modulban használhatóak ezek a nevek. Azt tudjuk hogy pythonban nincsenek privát mezők, minden publikus, ha éreztetni akarjuk egy változóról vagy metódusról hogy védett, akkor _ alulvonással kezdjük a nevét. Jogosan érezhetjük hogy ami két alulvonással kezdődik az nagyon szigorúan el lett rejtve. :) A jelölési szabály: dupla alulvonás elé/mögé. Amivel már találkoztunk, azaz az __init__, ami egy új példány létrehozásánál automatikusan hívódik meg. Az előbb láttuk a __call__-t is. Alsóhangon két tucat létezik itt.
A kövtkező részben látni fogjuk hogy a metaclassokhoz bevetéséhez mindössze meg kell neveznünk a metaclasst, természetesen a __metaclass__ osztály attribútummal.
Erre a pár részletre volt szükség ahhoz, hogy a következő fejezetben a két hiányzó elemet a helyére illesszük és meg is vagyunk a metaclassokkal. Azt tudnotok kell, hogy ez már metaprogramozás volt (dinamikusan építettünk osztály), ami ez után következik, az csak hab a tortán, némi szintatikai porcukorral meghintve.
Következik hamarosan a II. fejezet, ahol bevetjük végre a __metaclass__-t.
Posted
by