Python profesionálně: metatřídy

V předchozích dílech tohoto seriálu jsme si řekli spoustu zajímavých tipů, jak vyvíjet v Pythonu lépe a rychleji. Od syntaktických tipů přes různé tipy na vestavěné funkce, moduly atp. až po zajímavé řešení některých návrhových vzorů. Zbývá už jen poslední a pravděpodobně nejnáročnější povídání – o metatřídách. Co to vlastně je, jak se tvoří a kde se dají využít.
Seriál: Programujte v Pythonu profesionálně (5 dílů)
- Python profesionálně: úvod 3. 4. 2012
- Python profesionálně: dynamické parametry, generátory, lambda funkce a with 10. 4. 2012
- Python profesionálně: co jazyk nabízí 16. 4. 2012
- Python profesionálně: návrhové vzory 14. 5. 2012
- Python profesionálně: metatřídy 21. 5. 2012
Nálepky:
Hned na úvod: pokud se momentálně moříte s nějakým návrhem aplikace, metatřída vaše spása nebude. Většinou metatřída není řešením. Minimálně pro běžné aplikace, metatřídy se hodí spíše pro nějaké knihovničky, vychytávky a tak podobně. Ale doporučuji si o nich něco málo zjistit, i když to využijete málo či vůbec, abyste pochopili samotný Python nebo minimálně cizí kód, kde jsou metatřídy použité.
Jednu metatřídu jsme už v seriálu použili, bylo to pro snadnější zápis návrhového vzoru singleton. Připomeňme si kód, který se budeme snažit dnes pochopit:
class SingletonMeta(type): def __new__(cls, classname, bases, classdict): classdict.setdefault('__slots__', ()) newcls = type.__new__(cls, classname, bases, classdict) return newcls() class singleton(object): __metaclass__ = SingletonMeta
Trocha teorie
Konstruktor
Než se ale dáme do zjišťování, co se v ukázce děje, musíme si chvilku povídat. Začněme metodou __init__
– kdo ji nazývá konstruktor? Prosím, nesmějme se těm, kdo se právě hlásí. Všichni jsme ji tak dříve nazývali. Metoda __init__
není konstruktor. Je to z velmi jednoduchého důvodu – __init__
přece žádnou instanci nevytváří. Tu už dostává v parametru jako všechny ostatní instanční metody. Důkazem nechť je dokumentace: „Called when the instance is created.“
Co by to ale bylo za programovací jazyk, kdyby zde konstruktor nebyl. Ba je, a je skryt v metodě __new__
. Tato metoda skutečně něco vytváří a není to klasická instanční, nýbrž statická metoda. Ale žádné obavy, je to automatické a nemusíme sami metodu dekorovat, že se jedná o statickou.
Metoda __new__
přijímá jeden parametr, a to třídu, kterou máme zkonstruovat. V této metodě vytvoříme novou instanci, upravíme, jak je třeba a poté vrátíme. Vrátit nově vytvořenou instanci je důležité! Jinak se defaultně vrátí (jak už to v Pythonu bývá) None
hodnota a tím se instance nevytvoří, tím se ani nevyvolá metoda __init__
(zcela logicky) a prostě nic nemáme.
>>> class C(object): ... def __new__(cls): ... print "new" ... def __init__(self): ... print "init" ... def f(self): ... print "f" ... >>> c = C() new >>> c.f() Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'f' # vs. >>> class C(object): ... def __new__(cls): ... print "new" ... return super(C, cls).__new__(cls) ... def __init__(self): ... print "init" ... def f(self): ... print "f" ... >>> c = C() new init >>> c.f() f
Vnímavější může namítnout „ale vždyť v ukázce (SingletonMeta) jsou parametry čtyři!“ Nebojte, dostaneme se k tomu.
type
Co to je vlastně type
? Je to buď funkce, která nám sděluje typ předávaného objektu, nebo to je standardní metatřída (něco jako object
pro třídy), a nebo to je funkce, která nám umožňuje dynamicky vytvářet třídy. A nebo všechno dohromady.
>>> class C: ... pass ... >>> type(C()) <type 'instance'> >>> type(C) <type 'classobj'> >>> class C(object): ... pass ... >>> type(C()) <class '__main__.C'> >>> type(C) <type 'type'> >>> type(type) <type 'type'>
Z ukázky je dobře patrná změna ze starých tříd na ty nové objektové a taky provázanost objectu
s typem
. Instance třídy C
je typu třída C
a třída C
je typu metatřída type
. Metametatřída už neexistuje, takže type
zůstává typem
.
Zkusme si vytvořit třídu dynamicky (tj. ne přes klíčové slovo class
). Použijeme, jak jsme si řekli, funkci type
, jen nyní jako vstup použijeme víc a jiné parametry. Jako první předáme název třídy, poté tuple se seznamem, z čeho naše nová třída bude poděděna, a nakonec třídní slovník s atributy nové třídy.
>>> C = type('C', (), {'f': lambda self: 'Hello!'}) >>> c = C() >>> c.f() 'Hello!'
Pokud se pozastavujete nad tím, proč se musí říct název, když stejně pak musím vytvořit proměnnou s referencí (když už ta proměnná má název), tak je to právě kvůli tomu. Proměnná je proměnná a třída je třída. Vždyť přece lze i normálně udělat toto:
class C(object): pass D = C
Zde je taky proměnná D
, ale třída se jmenuje C.
Demonstrace metatřídy
S touto znalostí se můžeme pustit do ukázky první metatřídy.
class M(type): def __new__(cls, classname, bases, classdict): return type.__new__(cls, classname, bases, classdict) class C(object): __metaclass__ = M
Tato ukázka je vlastně zbytečná, protože takhle to funguje defaultně, pokud se dědí z object
u (samozřejmě se žádná M
metatřída nevytváří, použije se type
). Ale je to dobré pro ukázku, jak to vlastně pracuje. Každá třída poděděná z object
u má tak i nějakou metatřídu, nejčastěji type
, která vlastně říká, jak se má třída vytvořit a chovat. Podobně jako to říká třída svým instancím.
Myslím, že dobře to lze pochopit taky takto: pomocí třídy se vytvářejí instance a pomocí metatřídy se vytvářejí třídy. Metoda __new__
ve třídě ( object
) vytváří nové instance a metoda __new__
v metatřídě ( type
) vytváří nové třídy. Není to úplně pravda, ale pro pochopení se to tak dá vysvětlit.
Analogicky funguje metoda __init__
. Ve třídě ji všichni známe a v metatřídě přijímá 3 (resp. 4) parametry stejně jako výše popisovaná metoda __new__
.
Metametody
V metatřídě můžeme definovat metody (metametody), které potom získá i třída, která používá danou metatřídu. Instance se však k těmto metodám nedostane.
>>> class M(type): ... def metamethod(cls): ... return "metamethod of %s" % cls.__name__ ... >>> class C(object): ... __metaclass__ = M ... @classmethod ... def classmethod(cls): ... return "classmethod of %s" % cls.__name__ ... >>> c=C() >>> print M.metamethod() TypeError: unbound method must be called with M instance as first argument (got nothing instead) >>> print M.metamethod(C) metamethod of C >>> print C.metamethod() metamethod of C >>> print c.metamethod() AttributeError: 'C' object has no attribute 'metamethod' >>> print C.classmethod() classmethod of C >>> print c.classmethod() classmethod of C
Vhodná funkčnost se ukazuje většinou na srandičkách typu předefinování __str__
a ukážu to na tom také, protože jsem se zatím s jiným vhodným využitím nesetkal (nebo setkal, ale neuvědomil si to).
>>> class M(type): ... def __str__(cls): ... return '<trida %s>' % cls.__name__ ... >>> class C(object): ... pass ... >>> str(C) "<class '__main__.C'>" >>> >>> class C(object): ... __metaclass__ = M ... >>> str(C) '<trida C>'
Shrnutí definice
Pokud trochu tápete, prostě si pamatujte, že třídy jsou definice pro instance a metatřídy jsou definice pro třídy. Zbytek se dostaví časem samo.
Rozbor metatřídy SingletonMeta
Nyní, když už máme potřebné informace, se už můžeme dát do rozboru metatřídy pro náš singleton.
Pro připomenutí, co bylo potřeba pro náš singleton ručně zařídit: definovat třídu singletonu (samozřejmě), v ní definovat sloty, aby nešlo jen tak všemožně editovat (nejlépe zakázat vše a zkonfigurovat při vytvoření), a definici přepsat jedinou instancí. Díky metatřídě tuto celou funkčnost můžeme zapouzdřit do znovupoužitelného kódu a na detaily nemyslet. A stačí na to jediná metoda.
Co vlastně ta metoda dělá? To co jsme si řekli. Definici hlavičky pravděpodobně mohu přeskočit, další řádek pouze vezme slovník s třídními atributy a nastaví mu nový atribut __slots__
s prázdným tuplem, aby nešlo nic nastavovat. Díky použití metody setdefault
nebude případně již nastavený atribut přepsán. Na dalším řádku zavoláme skutečnou továrničku na třídy, nic extra. A v posledním kroku vytvoříme novou instanci nově vytvořené třídy a rovnou ji vrátíme. Díky tomu, že nevracíme nově vytvořenou třídu, ale rovnou její jednu instanci, získáme po napsání třídy s touto metatřídou instanci (a definice je skryta pouze v atributu __class__
naší nové instance).
A to je celé kouzlo. :-)
Komplikovanější problém
Abychom metatřídám porozuměli ještě lépe, zkusíme vyřešit jeden problém, který jsem nedávno řešil. Máme takovéto prostředí: aplikace, kde je hodně moc různých tříd. V té aplikaci je někde zapeklitý bug, který musíme najít. Je to tak zapeklité, že se nelze soustředit na určitou část kódu, poněvadž absolutně nechápeme, co naší aplikaci přeletělo přes nos (takový vzdálený příbuzný Skynetu) – tedy jednoduše si něco vypisovat nepomůže ( print
, pprint
, repr
, …). Máme dobré logování, ale jako naschvál loguje pro tento konkrétní problém nešťastným způsobem. A klasický debugger nelze použít. Co teď? Jak si usnadnit práci s hledáním problému?
Můžeme si postupně zjišťovat, jakou cestou aplikace zpracovává naše požadavky a po té cestičce vyskládat nějaké záchytné body. A podle zjištění nějaké ubrat a nové přidat. A podle nového zjištění zase nějaké ubrat a přidat. A stále dokola, dokud se nenarazí na příčinu problému.
Nebo si pomoci trochu jinak…
Řekněme, že máme takovýto kód:
class C(object): def __init__(self): self.x = 'x' self.y = 'y' def func_with_bug(self): # ...
A teď bychom si chtěli logovat, co se v třídě děje. Například co se nastavuje za proměnné. To lze udělat metodou __setattr__
:
class C(object): def __setattr__(self, key, value): print "Nastavuju %s na %s." % (key, repr(value)) super(C, self).__setattr__(key, value)
Tak, nyní se nám před nastavením hodnoty vypíše co se nastavuje a na co. Mohli bychom si třeba vypisovat i původní hodnotu… Dále by se nám hodilo zjišťovat, které proměnné se používají. To lze udělat pomocí metody __getattribute__
:
class C(object): def __getattribute__(self, key): print "Vracim %s." % key super(C, self).__getattribute__(key)
Pozor! Existují dvě metody, jmenují se velmi podobně, ale každá dělá něco jiného. Mluvím o zmíněné __getattribute__
a __getattr__
. První ( __getattribute__
) se zavolá vždy, ať už daný atribut existuje, či nikoliv, zato druhá ( __getattr__
) se zavolá pouze, když hledaný atribut neexistuje.
>>> class C(object): ... x = 'x' ... def __getattribute__(self, key): ... print 'getattribute', key ... super(C, self).__getattribute__(key) ... def __getattr__(self, key): ... print 'getattr', key ... super(C, self).__getattribute__(key) ... >>> c = C() >>> c.x getattribute x >>> c.y getattribute y getattr y AttributeError: 'C' object has no attribute 'y'
V ukázce nemám chybu. object
nemá metodu __getattr__
, má pouze __getattribute__
, která buď vrátí hodnotu atributu nebo vyhodí výjimku.
Dále by se mi líbilo logovat, které metody se volají, nejlépe s jakými parametry. Též není problém, od toho máme dekorátory:
def logdecorator(func): def wrapper(*args, **kwds): print 'Volani metody %s s parametry %s, %s' % (func.__name__, args, kwds) return func(*args, **kwds) return f class C(object): @logdecorator def f(self): pass @logdecorator def g(self, a, b=2): pass
Nyní mám poměrně dobře pokrytou jednu třídu detailním logováním. Má to ale problém – vidíte, co se musí všechno nadefinovat a na co je potřeba myslet? To už je opravdu jednodušší si ručně rozházet logovací příkazy. Pojďme si to zjednodušit, tedy použít metatřídy. Víte, jak to napsat? V tom případě si to zkuste sami, než budete pokračovat…
Předělání na metatřídu
Začneme tím, že si vytvoříme prázdnou metatřídu s metodou __new__
, ve které vytvoříme a vrátíme novou třídu.
class DebugMetaClass(type): def __new__(metacls, className, bases, classdict): cls = type.__new__(metacls, className, bases, classdict) return cls
Zatím nic extra, pouze zbytečný kód. Rozšiřme ho. Budeme postupovat ve stejném pořadí, takže přidáme naší metatřídě třídní metodu, která nám vytvoří výše ukázanou metodu logující setované hodnoty instancím nově vytvářené třídy a té třídě ji nastaví.
def __new__(...): ... cls.__setattr__ = metacls.create_set_attr_method(cls) ... @classmethod def create_set_attr_method(metacls, cls): def f(self, key, value): print "Nastavuju %s na %s." % (key, repr(value)) super(cls, self).__setattr__(key, value) return f
Obdobně bychom vytvořili i metodu vytvářející metodu zaznamenávající, které proměnné našich instancí se používají. Co je ale složitější, je odekorování metod. Musíme projít všechny atributy a pouze callable atributy odekorovat. Menší kvíz: odekorovat metody před nebo za nastavením metod __setattr__
a __getattribute__
? … Odpověď: Určitě před, protože kdybychom to dali za, tak bychom si tyto metody také odekorovali, což je zbytečné až nežádoucí.
def __new__(...): ... for attributename, attribute in classdict.items(): if hasattr(attribute, '__call__'): dbgdecorator = metacls.create_debug_decorator(cls) setattr(cls, attributename, dbgdecorator(attribute)) ... @classmethod def create_debug_decorator(metacls, cls): def f(func): def wrapper(*args, **kwds): print 'Volani metody %s s parametry %s, %s' % (func.__name__, args, kwds) return func(*args, **kwds) return wrapper return f
Pro ověření, zda je atribut metoda (nebo funkce), jsem naschvál použil zápis hasattr(attribute, '__call__')
místo built-in funkce callable
, protože chceme podporovat co nejvíce verzí Pythonu. Jinými slovy funkci callable
v Pythonu 3.0 a 3.1 nenalezneme. Naštěstí vývojáři dostali rozum a v Pythonu 3.2 se vrací zpět.
Výsledek
Takovéto logování může být fajn, ale nemusí postačovat. Například by bylo dobré logovat dobu trvání a výsledek funkcí. Nebo odkud se s instancí pracuje. Vše je řešitelné a cílem této ukázky není řešit tyto drobnosti. Cílem je ukázat, jak jednoduše a kdy například lze použít metatřídy.
Jak jsem řekl v úvodu řešení logovacího problému: už jsem tento problém řešil. Kód už jsem tedy sepsal do znovupoužitelné podoby a umístil na GitHub. Také jsem ho přidal do PyPI. Nemusíte tak tento kód přepisovat do vašeho modulu a dořešit detaily, o kterých jsem mluvil o odstavec výše. Podívat se na kód můžete na https://github.com/…thon-debugger a nainstalovat z PyPI příkazem pypi-install debugger
.
Závěr
Metatřídy se většinou jako řešení nějakého problému nehodí, ale jsou užitečné. Pomohou nám pochopit, jak Python pracuje a jednou za čas elegantně vyřešit problémy, které nejdou jednoduše řešit klasickou cestou. Další zajímavé použití metatříd je například v Djangu, kde se používají u databázových modelů, mrkněte se jim na kód (ale pozor, je to docela elektrárna). Další zajímavé čtení o metatřídách lze nalézt v mini seriálu na stránkách IBM s dalšími ukázkami, kde se dají metatřídy vhodně využít:
- Metaclass programming in Python
- Metaclass programming in Python, Part 2
- Metaclass programming in Python, Part 3
Dalším zajímavým zdrojem může být oficiální popis od Guido van Rossum v článku Unifying types and classes in Python 2.2.
A tím ukončuji naše povídání o zajímovstech z Pythonu. Netvrdím však, že jsem vám sdělil vše zajímavé a must know. Stále je o čem mluvit a kde se dál vzdělávat. Další spoustu informací lze najít v dokumentaci. Python dokáže vše nějak vyřešit a prohledání dokumentace (případně stackoverflow) mi vždy pomohlo. A pomůže určitě i vám. Stačí jen zkusit hledat, protože již napsaný modul v céčku od chytřejších lidí je vždy lepší, než si něco bastlit sám v Pythonu.
Dokumentace nejvíce používané poslední stabilní verze je na adrese http://docs.python.org/ a určitě doporučuju si ji projít.
Fajn, když už známe mechanismus tříd, jak bychom implementovali datový typ Roman odvozený z typu int, který bude sloužit pro počítání s římskými čísly? (moje oblíbená otázka :-) )
class Roman(int): pass
assert Roman(‚VI‘) + Roman(‚I‘) == Roman(‚VII‘)
Doplňte tělo třídy Roman.
ale metatřídy můžete použít k vytvoření třídy instance, která zkontroluje zda vytvářená třída má implementovány všechny metody z třídy, kterou dědí, což je interface například z Javy a což Python nemá. Nebo metatřídy využívá framework Django k deklarativnímu programování modelů (MVC). Viz zde http://lanyrd.com/2011/pyconau/sfxwt/
Co je nesmysl?
Nesmysl je implementovat třídu Roman, když to můžete vyřešit konverzí na integer. Vytvoříte-li třídu Roman, je nebezpečí, že někdo z pohodlnosti ji bude používat a ani si neuvědomí, že počítání s ní je dost drahé, protože minimálně před každou a na konci každé operace musí dojít ke konverzi z a na integer :-)))
> Nesmysl je implementovat třídu Roman, když to můžete vyřešit konverzí na
integer.
Tohle se melo dit vevnitr tridy Roman, kde je momentalne „pass“. Ke konverzi dojde jen na zacatku (pri parsovani stringu). Aritmetika probehne jako s beznym integerem.
> Počítání s ní je dost drahé, protože minimálně před každou a na konci každé operace musí dojít ke konverzi z a na integer.
Pane, mate v tom trochu zmatek. Rozsiruji tedy svuj assert na toto, zadani zustava stejne:
assert Roman(‚VI‘) + Roman(‚I‘) == Roman(‚VII‘) == 7.
class Roman(int):
@staticmethod
def roman_to_decimal(roman_number):
„prevede rimske cislo na int“
def __new__(self, *args, **kwargs):
return int.__new__(self, Roman.roman_to_decimal(args[0]), **kwargs)
assert Roman(‚VI‘) + Roman(‚I‘) == Roman(‚VII‘) == 7
asi by to slo i lip, ale takhle to snad vystihuje myslenku
A neztrácí při takové implementaci využití třídy smysl? Jakou má výhodu volání konstruktoru Roman() místo přímého volání funkce roman_to_decimal()?
Pokud tomu dobře rozumím, tak výsledkem je pak obyčejný int. Nebo jak z něj dostanu např. převod zpět na římské? Půjde na něm zavolat nějaká metoda, která vrátí patřičný String, nebo budu muset tento int vložit jako parametr do jiné funkce? (případně opět do konstruktoru)
Zatím moc nevidím přínos oproti obyčejným funkcím roman_to_decimal() a decimal_to_roman() a tento příklad mi přijde takový vykonstruovaný a zbytečný.
P.S. V Javě máme pěknou implementaci, která umožňuje zapisovat římské číslice přímo do kódu (bez uvozovek, úplně stejně, jako píšeme arabské). Sice je to zbytečnost, praktické využití to nemá, ale je to alespoň technicky zajímavé řešení.
V uvodnom priklade je pouzite meno s malym aj s velkym „D“.
Díky za upozornění.
….
Připadá mi divné neoznačovat __init__ jako konstruktor. Nevím, jestli se v Pythonu tradičně myslí pod pojmem konstruktor metoda __new__, jak je zmíněno v článku, ale třeba v C++ se podle mě pojmem konstruktor označuje přesně to, co dělá metoda __init__. Je to instanční metoda (má pointer this) a paměť pro danou instanci taky nealokuje. Tudíž __new__ bych z pohledu C++ označil jako přetížený operátor new :-).
http://mail.python.org/pipermail/tutor/2008-April/061426.html
Use __new__ when you need to control the creation of a new instance.
Use __init__ when you need to control initialization of a new instance.
__new__ is the first step of instance creation. It’s called first,
and is responsible for returning a new instance of your class. In
contrast, __init__ doesn’t return anything; it’s only responsible for
initializing the instance after it’s been created.
In general, you shouldn’t need to override __new__ unless you’re
subclassing an immutable type like str, int, unicode or tuple.
mista hasattr(xxx, ‚__call__‘) mame v pythonu funkci callable()
nepouzivejte prosim camelCase pro nazvy parametru, pep-8 stae existuje
ukazka kodu na create_set_attr_method je proste spatne:
* je totalne zbytecne generovat tu metodu dynamicky
* navic je to i spatne napsane – misto ‚C‘ tam ma byt cls
* I tak by to ale bylo spatne v pripade ze nekdo uz ma tu metodu pretizenou, pak mu proste rozbijete kod a zavedete do systemu nepreberne mnozstvi bugu
Plati rozumna zasada ze kdyz hledam bug tak se snazim do veci nezasahovat vic nez musim, takhle extenzivni zasah je naprosto kontraproduktivni – budu se opakovat – neni to testovatelne, vykonne ani citelne ale hlavne to neplni svoji funkci a je to spatne. Kdyz potrebuju tracovat co program dela, pouziju tracer, profiler, debugger nebo jakykoliv jiny nastroj ktery je k tomu urcen (treba coverage.py se hodi casto na takove veci), nebudu si obfuskovat kod sbirkou hacku ktera je navic (v tomto pripade) nejen zcela zcestna ale i fakticky spatne!
Spatny link na github uz je jen prkotina kterou si kazdy opravi.
Dalsi clanek ktery je jen nepreberna sbirka hacku za ktery by se kazdy python programator stydel.
Prosím, přečti si ještě jednou ten odstavec pod kódem, kde jsem použil
hasattr(attribute, '__call__')
místocallable()
. (V Pythonu 3.0 a 3.1 metodacallable
není.)CamelCase a
C
místocls
jsou překlepy, díky za upozornění. CamelCase používám všude a i když jsem ty články procházel několikrát, stejně mi tam pokaždé něco uteklo.Když už píšeš, že je zcela zbytečné generovat ty metody dynamicky, možná by jsi mohl poučit a napsat, jak to udělat bez toho.
Vím, že když je definovaná, že se přepíše. Zatím mě to netrápilo, takže jsem se nedokopal to upravit (je to ale v plánu). A řešit takovéhle detaily v ukázce mi přijde velmi zbytečné. Stačí se podívat, že tam neřeším ani jiné detaily.
V mém případě jsem opravdu nepotřeboval tracer, ani profilter, ani debugger, ani coverage.py. Resp. tracer nebo nějaký debugger by se mi hodil, ale ani jedno nešlo použít. Takže jsem si napsal pár těhle speciálních metod, který mi s hledáním problému pomohly. Když už jsem to mě napsané, napsal jsem si to do metatřídy, abych to případně příště už nemusel psát znovu. Mimochodem mluvit zde o testovatelnosti nebo vykonnosti je hloupost – kdo by to použil pro něco jiného, než debugování?
Na výkon to nebude mať veľký vplyv tak či onak. Triedy sa vytvárajú len raz.
Btw, čo sa týka hľadania chýb, tak tu je veľmi dobré si prečítať knihu Dokonalý kód.
Za callable se omlouvam, prehledl jsem tu zminku.
Bez dynamickeho generovani:
def f(self, name):
print name
return object.__getattribute__(self, name)
cls.__getattribute__ = f
f muze byt klidne funkce na urovni modulu, neni treba ji jakkoli generovat
Kdyz delas takovehle extenzivni zasahy do kodu kvuli debugovani, jakou mas jistotu ze si neco nerozbil kdyz na to nepustis testy? Copak takovehle upravy ktere ti potencialne zmeni chovani kazde metody a atributu netestujes? Ne, skutecne neni hloupost u takovychto veci mluvit o testovatelnosti. Minimalne je potreba vedet ze ti to nenarusilo funkcnost, idealne budes provaded debugovani primo za pomoci testu abys mel vse pod kontrolou a mel pak test kvuli potencialnim regresim.
Ano, vim ze spoustu mych namitek je nad ramec jednoho clanku, ale kdyz k tomu jeste nalinkujes ukazku na githubu kde jsou vsechny tyto chyby take (snad krome toho dyn. generovani mozna), uz to stoji za to to zminit aby si nekdo nahodou nemyslel ze takhle se to v pythonu dela nebo ma delat.
Nedokazu si predstavit ze by neslo pouzit tracer ci debugger ale je ok provest neco takoveho, osvetlis nam proc to tak bylo?
sorry za formatovani, neuvedomil jsem si ze hvezdicky to rozhodi, neni to umyslne.
Souhlasim,
a kdyz uz jsme u toho, hodil by se vycerpavajici clanek o testovani.
Souhlasím, až takové články napíšete, moc rádi je vydáme. 8-)
bohuzel s psanim clanku je to u me tezke, to musim uznat ze v tomhle ma nade mnou autor jasnou prevahu.. kdysi jsem tu psal nejaky uvod do testovani, ted se snazim co to jde dopsat clanek o denormalizacich do redisu ktery uz jsem slibil na zdrojak.
Kdyby se ale nasel nekdo kdo umi psat a bavi ho to, rad jim poradim a pomuzu s technictejsi casti, pripadne se staci stavit na pravidelny python sraz (kazdou treti stredu v mesici na Smichove), udelame tam tematicky vecer nebo prednasku a pak z toho staci sepsat zapis.
Kazdemu zajemci ci zajemkyni rad zaplatim pivo ci napoj dle jejich vyberu :)
V pohodě, vyznám se v tom. :)
Musím ty metody generovat, protože pokaždé mám jinou třídu. Mohu sice použít pro zavolání přímo
object
místosuper(cls, self)
, jak píšeš, jenže když to bude poděděná třída ze třídy, která má nějakou takovou metodu, nikdy se nezavolá. To je to samé, jako že momentálně přepisuju takovou metodu ve tříde, kde metatřídu použiju (což mám už dlouho v plánu tento nedostatek odstranit).Když debuguju, rozbíjím kód. Naschvál. Většinou se mi stačí dívat do logu, do kódu, nebo si přidat nějaký výpis někam. Občas to chce ale něco víc a to už rozbíjím kód. Vyřazuju různé části a hledám, co je asi příčinou. Při debugování klidně rozbiju několik metod, abych se dopátral k příčině problému. (Aby bylo jasno – mluvím o hledání nějaké pekelnosti. Kde mám dobré pokrytí testů a nehledám záludnosti, stačí mi testy.)
__setattr__
/__getattr__
už bylo řečeno.Třída má svou metatřídu. To už je horší. Na druhou stranu kolik tříd to má? Těch je tak málo, že se s tím normální vývojář nesetká a vývojář Djanga si umí poradit. Navíc u vývoje Djanga něco takového (podle mne) není potřeba.
uncidoe
. Hm, co s ním? Že se nezobrazí zrovna čitelně?stdout
. Proto lze logovací metodu vyměnit (já například využil něco jiného; s printem bych nepochodil). Dal jsem tam defaultprint
, ale máš pravdu, želogging
by byla lepší volba.Proč nešlo použít tracer či debuger? Protože šlo o něco jako mod_python. O webovku, kde se při určitém hmatu něco v nějakém stavu stalo a nebylo jasné kde a co. Můj debugger má výhodu v tom, že si ho můžu přidat jen na třídy, kde mám podezření, a ještě filtrovat dle regulerního výrazu. Tedy mohu sledovat změnu stavu jen u toho, co mě zajímá.
Vím, že ten debugger není dokonalý a ještě potřebuje poladit. Ani nebude dokonalý. Je to jen další pomocník, který jde na to trochu jinak s řešením jiného problému. A jako ukázka, kde je například vhodné použít metatřídu, je více než dobré. Takže ne, nestydím se za to.
> Mimochodem mluvit zde o testovatelnosti nebo vykonnosti je hloupost – kdo by to použil pro něco jiného, než debugování?
To nemyslis uplne vazne, ze ne?
Nebo snad knihovny urcene k debugovani nemaji testy? Zvlast pokud to sdilis na githubu a mohlo by hrozit, ze ti tam nekdo bude chtit neco doplnit? O tom, ze testy jsou hlavne hodne pohodlny na vlastni vyvoj ani nemluve…