Python ve velkém podporuje introspekci (též známo jako reflexe), tj. zjišťování, které atributy a metody jsou na objektu definované, zda je objekt potomkem nějakého rodiče (a kterého) apod. Přístup k podobným informacím je umožněn v zásadě dvěma způsoby – pomocí vestavěných funkcí a pomocí „navigace“ přes magické atributy/metody (to jsou ty se jménem z obou stran dvojpodtržítkovaným).
Obecně bychom introspekci objektu mohli charakterizovat jako hledání odpovědí na následující otázky:
Protože vestavěné objekty, na které bychom se mohli dívat, obsahují většinou velmi mnoho podobjektů, ukázkové příklady v této přednášce budou obsahovat starý známý testovací modul skola, jehož struktura je následující:
Přitom zavedeme jednu instanci jménem alisa tímto způsobem:
Jakousi základní introspekci poskytuje už samotný interpretr Python'u. Zadání jména objektu na příkazové řádce interpretru způsobí vypsání jakési jeho základní hodnoty (často reprezentované jeho magickým atributem __str__()
), která nám už sama o objektu mnohé napoví.
Při našem předchozím příkladu s Alisou bude např. platit:
PS: Kromě funkce print() využívá tuto magickou metodu především také funkce str() a format().
Jak už to tak bývá, skutečnost je zamotanější – ve výchozím nastavení na objektech totiž metoda __str__() zavolá metodu __repr__() (kterou jinak volá vestavěná funkce repr()), takže jejich výstup je stejný.
Ale to je pravda jenom pro potomky základního objektu object
, pro jiné typy je mezi nimi rozdíl – je-li to pro daný objekt/typ jen trochu možné, měla by metoda ___repr__() vracet řetězcovou reprezentaci objektu v podobě pythoního výrazu, takže by jejím přímým použitím (v příslušném běhovém prostředí) mělo jít objekt přímo rekonstruovat.
Kde uvedená reprezentace není možná, obdržíme typickou hlášku v podobě < popis objektu >
.
Za asi nejutajenějšího kandidáta na prvek jazyka, který velmi využívá vlastností a schopností introspekce u Python'u, však může být pravděpodobně pokládána funkce help()
. Ta totiž dohledává většinu svých informací v rámci dokumentačních řetězců kódu, které jsou přístupné přes magický atribut __doc__
jednotlivých objektů.
Při pokračování příkladu s Alisou bude např. platit:
Mezi introspekční nástroje můžeme počítat též funkci id(OBJEKT)
. Ta vrací identifikátor objektu, který je po dobu jeho života konstantní a jedinečný (typicky odpovídá jeho adrese v paměti). Například v rámci stejného běhu testovacího programu jako výše vrátila funkce id() toto:
hash()
poskytuje pro každý objekt pochopitelně vždy jedinečné číslo.
Pravděpodobně zdaleka nejpoužívanějším introspekčním nástrojem (alespoň na příkazové řádce a při „od[brouk/mouch]ovávání“) bude funkce dir(). Ta vrací (jako řetězce) seznam všech (nedynamicky vytvořených) atributů přítomných na daném objektu.
V našem příkladu s Alisou obdržíme na dotaz dir(alisa)
odpověď:
Kromě námi vytvořených atributů se objekt alisa jen velmi málo liší od vlastností všech objektů, které zajišťuje jejich společný předek object, jak nám ukáže dotaz dir(object)
:
Veškeré odlišnosti mezi obecným a konkrétním objektem přitom padají na vrub zavedení nového objektu s vlastními atributy:
__dict__
– slovník jmenného prostoru třídy; tj. atributy a jejich hodnoty;
__module__
– jméno modulu, v němž byla třída definována;
__weakref__
– signalizace přítomnosti podpory pro weak references u instance¨*.
Odpovídající hodnoty pro testovací příklad jsou:
Funkce dir()
vrací přehled atributů daného objektu jako obyčejný seznam řetězců bez jakéhokoliv bližšího rozlišení. Můžeme nějak snadno rozlišit mezi atributy datovými a metodami? Ano, pomocí funkce callable()
:
isinstance(x, collections.Callable)
. Překvapivě od Python'u 3.2 je vše zase zpátky při starém a znovu si vystačíme pouze s voláním funkce callable(x)
^_~
Použití callable() pro zjištění „volatelnosti“ atributu (nebo obecně jakéhokoliv objektu) je sice pěkné, ale jak nám pomůže, když dir() poskytuje odpovědi jako řetězce, ale callable() očekává referenci na objekt? Se záchranou přichází funkce getattr(OBJEKT, "atribut")
(atribut je tedy zadáván jako řetězec). Volání této funkce je vlastně ekvivalentní zavolání OBJEKT.atribut
:
getattr(OBJEKT, "ATRIBUT", default)
, přičemž default je vrácen místo hodnoty OBJEKT.ATRIBUT
, není-li tato k dispozici. Pokud default neuvedete a atribut uvedeného jména na objektu neexistuje, obdržíte výjimku AttributeError
.
Funkce getattr() má sourozence – hasattr(OBJEKT, "atribut")
. Ta slouží ke zjištění, zda daný objekt má atribut uvedeného jména (a tak je podstatně vhodnějším kandidátem na použití v programu, než procházení návratového seznamu funkce dir()):
Funkce hasattr() a getattr() mají ještě třetího sourozence, který však už nemá moc společného s introspekcí. Je jím funkce setattr(OBJEKT, "atribut", hodnota)
plnící funkci nastavení hodnoty atributu, jehož jméno je zadáno jako řetězec:
Slovník jmenného prostoru třídy __dict__
můžeme použít k přímému (a tedy dosti neklasickému) přístupu na atributy objektu:
Jako u mnoha jiných dvojpodtržítkovaných atributů tohle normálně nebudete chtít dělat, ale sakra se to hodí například při definici singletonových atributů na objektu:
Jelikož __dict__
jako slovník je poměrně náročná paměťová struktura, existuje pro třídy s malým počtem vlastních atributů a naopak velkým počtem instancí (tisíce či milióny) obezlička pro interpret CPython* v podobě jednodušší datové struktury, a to tzv. __slots__
.
Zavedení __slots__
na třídě způsobí, že na jejích instancích nebudou vůbec vytvářeny „magické“ atributy __dict__
a __weakref__
, což dokáže ušetřit spoustu místa. Kromě toho to ale také způsobí celou další armádu vedlejších efektů, které docela spolehlivě nabourají nejen introspekci a přístup k atributům (například slovník se dá rozšiřovat, ale sloty ne), proto je jejich použití pochopitelně velmi speciální a nemáte-li pro něj safra dobrý důvod, měli byste se mu vyhnout.
Mnoho dalších „klasických“ introspekčních vestavěných funkcí jsme už potkali. Zcela typickou je např. funkce type(OBJEKT)
, která vrací typ daného objektu. Návratová hodnota je typový objekt a většinou odpovídá objektu v OBJEKT.__class__
.
Při našem předchozím příkladu s Alisou bude např. platit:
Pro testování typu objektu se však nejčastěji používá funkce isinstance(OBJEKT, TŘÍDA)
, protože bere v potaz celý řetězec dědičnosti, jak ukazují následující příklady:
Pokud nás budou zajímat čistě „dědičné“ vlastnosti objektů, použijeme funkci issubclass(OBJEKT, TŘÍDA)
. Například při pokračování v našem předchozím příkladu s Alisou bude pro vzájemné vztahy mezi třídami platit:
Použijme (mírně upravený) příklad z http://www.ibm.com/developerworks/library/l-pyint.html. Funkce interrogate()..
..dává při následujícím volání..
..výstup:
Jak poznamenává uvedený zdroj: „Můžeme vyslýchat dokonce sami sebe – lepší už to nebude.“ ^_^