Python umožňuje provádět spoustu věcí jinde nemyslitelných. Koneckonců, všechno v Python'u je přece objekt, se kterým si můžeme dělat (téměř) všechno, co chceme:
Ale jak už to tak bývá, mocné prostředky se dají také velice snadno zneužít. Takže pozor na ně ^_~
Stejně jako jsou objekty konkrétními instancemi svých tříd (nebo vlastně také typů), jsou třídy instancemi svých metatříd:
Metatřídy jsou volatelné objekty, které slouží ke tvorbě tříd. Jejich návratovou hodnotou je tudíž volatelný objekt, který pak následně slouží jako konstruktor (nějaké) třídy.
Spoustu věcí, na které se dříve metatřídy především používaly, dnes už dokážou obstarat dekorátory tříd, které jsou ovšem podstatně mladšího data. Ale narozdíl od nich se metatřídy dědí (jsou to koneckonců vlastně „obyčejné“ typy, něco jako třídy na druhou), takže metatřídou „odekorované“ chování nových tříd mohou podědit i jejich potomci, což samozřejmě dekorátory nedokážou.
Než se začneme topit v metatřídách, začněmež od něčeho jednoduššího – zavedením nové instance třídy. Tento proces je (alespoň od dob zavedení „nových“ objektů ve dvojkovém Python'u) ve skutečnosti dvoustupňový – nejdříve je vytvořen objekt samotný, aby byl následně předán ke svému naplnění vstupními parametry vlastního konstruktoru:
__new__(cls[, …])
požadované třídy…
__init__(self[, …])
této třídy;
Na tomto procesu je zajímavé, že pythoní programátoři mají možnost se v obou krocích podle svého (zne)uvážení přehrabovat, protože jsou oba dostupné pod svými vlastními „magickými“ metodami.
Zatímco konstruktor instance třídy __init__()
znají všichni jako své boty, vlastního stvořitele této instance __new__()
vidí asi poprvé. Jde o to, že __init__() jako svůj první parametr (automaticky) dostává odkaz na právě zaváděnou instanci, takže mu ji někdo jiný musí nejdříve připravit. A tím někým je právě __new__().
Vzhledem k tomu, že __new__() tuto novou instanci přivede k existenci, nepřekvapí snad, že ji také vrací. Přitom nejsnažším způsobem, jak zajistit vytvoření správného typu objektu, je přenechat práci na svém rodiči (ať už je jím kdo chce):
PS: Nenechte se rozhodit tím, že __new__(cls) dostává na vstupu automaticky referenci na svoji vlastní třídu. Ve skutečnosti je to statická metoda třídy (schválně si před ni zkuste připsat @staticmethod) s pevně určeným prvním parametrem. Navíc je to celé v interpretru zadrátovaná vlastnost, takže se prostě píše takhle. Netuším proč.
Metodu __new__() si pochopitelně můžete upravit k obrazu svému, ale jakmile se vám podaří nevrátit z ní referenci na správný typ objektu (zde tedy Pokus), odpovídající __init__() se pochopitelně nezavolá (protože se volá na příslušné třídě). Jako třeba když nevrátíte nic:
Samozřejmě často není důvod vůbec __init__() volat, takže to koneckonců může být i žádané chování. Ale když zrovna není, tak víte, kam se podívat na pravděpodobného viníka.
Při tvorbě vlastních metatříd typicky musíte __new__() přepsat, protože – jak už nyní víme – to je právě to místo, které je zodpovědné za tvorbu nových tříd.
Kdyby ovšem přepsání kroku __new__() bylo to jediné, co by metatřídy dělaly, nikdo by je nepotřeboval a všichni by háčkovali obyčejné třídy (ne že by se metatřídy od tříd nějak extra lišily, že).
Ve skutečnosti umí metatřídy kompletně předepsat (a přepsat) způsob, jakým jsou za běhu (tedy dynamicky) vyrobeny nové typy (nebo – chcete-li – třídy). Na což mimochodem je – asi trochu překvapivě vzhledem ke svému běžnému použití – specializována funkce type().
Vytvoření nové třídy je vcelku náročný proces. I když do něj nebudete většinou potřebovat (natožpak chtít) zasahovat, vyplatí se o něm alespoň něco vědět. V podstatě jsou při zavádění nové třídy provedeny následující kroky, a to v uvedeném pořadí:
Při běžném programování je pro nás asi nejdůležitější vědět, v jaké chvíli je vykonáván krok dekorátorů (a i tak se dekorování tříd nedělá moc často). Při „těžkém háčkování“ pomocí metaprogramování pak asi nalezení odpovídající metatřídy (což není triviální záležitost) a její aplikace.
Nejdůležitější metatřídu ze všech tedy všichni dobře znají – je jí totiž (jak už jsem říkal možná trochu překvapivě) stará dobrá známá funkce type().
Jak už víme, běžně slouží funkce type(OBJEKT)
ke zjišťování typu OBJEKTu. Návratová hodnota přitom většinou odpovídá atributu OBJEKT.__class__
.
Se třemi vstupními argumenty se však type promění na konstruktor tříd aneb tzv. metatřídu, což je popravdě skrytě její zdaleka nejčastější a nejdůležitější použití:
třída = type(JménoTřídy, RodičeTřídy, JmennýProstorTřídy)
Přitom platí následující korespondence mezi atributy a výslednou třídou:
třída.__name__
třída.__bases__
třída.__dict__
Pro obecné metatřídy platí vlastně to samé – jejich odpovědností je převzít jméno třídy, seznam rodičů a slovník parametrů budoucí třídy a z těchto tří složek onu třídu vytvořit.
Z hlediska programátora fungují metatřídy celkem neviditelně – pokud si použití nějaké výslovně nevyžádá, odehrává se veškerá metatřídní „magie“ mimo jeho dohled: Při zavádění nové třídy se prostě zavolá nejvhodnější dostupný konstruktor, ve většině případů právě funkce type(), který požadovanou třídu vyrobí a vrátí nám ji.
Pokud chceme použít nějakou specifickou metatřídu, můžeme si ji při zavádění nové třídy vyžádat pomocí atributu konstruktoru metaclass:
Tahle zdánlivě nevinná operace samozřejmě vyžaduje, aby konstruktor Metatřída existoval a choval se tak, jak se spořádaná metatřída chovat má. A to už taková švanda není. V dalším se tomu všemu podíváme na zoubek.
Bez metatřídy napíšeme..
..a zavolá se:
S metatřídou napíšeme..
..a zavolá se (alespoň v typickém případě):
PS: V reálném případě stačí, aby nějaký předek byl založen na metatřídě – Python pak příslušnou volací sekvenci dohledá sám.
Z praktického hlediska jsou metatřídy obyčejné třídy, na kterých ale definujete „magickou“ metodu __new__(), která je právě volána při „výrobě“ instancí tříd, případně __init__(), která je volána při naplnění nově vytvořené instance daty.
Stejně tak dobře můžeme ještě přepsat metodu __call__(), která se volá při zavádění instancí, čímž můžeme mimo jiné odseparovat, co třída dělá, od toho, jak je vytvořena.
Pokud nadefinujete také metodu __prepare__(), bude tato použita při přípravě jmenného prostoru třídy (jinak se použije běžný pythoní slovník dict()).
Příklad (upravený z dokumentace) bude určitě názornější. Připravme si následující metatřídu (která pro zajímavost definuje jak __new__(), tak i __prepare__()):
Pokud třídu OrderedClass použijeme jako metatřídu, stane se přesně to, co bychom očekávali:
Uvedená metatřída byla použita jako konstruktor ke tvorbě nové třídy, tedy vlastně instance metatřídy, jak dokazuje:
Byly proto zavolány obě dvě metody v uvedeném pořadí – nejdříve __prepare__()
pro zavedení upraveného jmenného prostoru budoucí třídy a následně __new__()
pro vlastní zavedení této třídy.
Jestě pro zajímavost – pro instanci třídy A platí:
Protože jsme uvedenou metatřídu zaváděli proto, abychom získali třídy, které si budou pamatovat pořadí svých atributů (zde konkrétně za pomoci atributu members), vyzkoušejme ještě tuto funkcionalitu:
Z __new__() můžete popravdě vrátit cokoliv. Třebas i již existující instanci (pokud možno správného typu samozřejmě) místo čerstvě vyrobené nové.
Ale možná ještě vtipnější je, že pro napsání metatřídy nakonec nemusíte __new__() vůbec použít. Úplně stačí, pokud volatelný objekt použitý jako metatřída vrací příslušný nový typ. A volat se dá v Python'u všechno, co má nadefinováno metodu __call__(). Což jsou kromě tříd mimo jiné třeba i funkce ^_~
Začněmež rovnou od „obyčejných“ funkcí:
To bylo nakonec celkem jednoduché. (Byť kvůli dynamické tvorbě třídy v kontextu funkce, který je po návratu z ní zapomenut, můžeme mít kapku problém s introspekcí.)
S třídami to ale může být horší. Když totiž šikovně přepíšete jejich metodu __call__(), nemusíte se v odůvodněných případech s __new__() vůbec babrat. Jako třeba v následujícím způsobu přípravy singletonu:
Jelikož zavolání instance vyvolá poděděnou metodu __call__() z příslušné metatřídy, která se aplikuje na volající instanci, dojde tím ke kontrole atributu __instance a podle jejího výsledku bude vytvoření pozměněné instance buď dovoleno nebo zakázáno.
V předchozím příkladu můžete stejně dobře použít i variantu bez super()..
..nebo dokonce úplně bez __init__():
Jen prostě instance musí mít dostupný atribut __instance. Jinak záleží, co konkrétně potřebujete v kterém kroku případně udělat (třeba toho chcete v rámci inicializace třídy „napáchat“ víc, pak ten __init__() využijete).
Na úrovni konkrétního objektu můžete samořejmě použít i variantu pomocí __dict__, ale tahle „metatřídní“ vám vyrobí singletony zadarmo všude pouhým odkazem na příslušnou metatřídu.
Jelikož sám OOP prakticky vůbec nepoužívám, návrhy na použití metatříd naprosto bez invence opisuji z dokumentace: