# PyLadies debugging (ladění) Hledání chyb v programech Projevy chyb: * **Výjimka** -- ta jednodušší situace, víme kde a proč chyba je * **Něco nefunguje, jak čekáme** -- Musíme zjistit proč tomu tak je * **Něco občas nefunguje, jak čekáme, nebo nám někdo tvrdí, že mu jednou nefungovalo** -- To je to pravé peklo ## Jak na to ### Možnost nejjednodušší -- printy **Výhody** * Rychlé a často jednoduché * Může zlepšít stukturu programu (doplníme logování, dopíšeme `__repr__` metody) **Nevýhody** * Není tak silné jako debugger * Nemůžeme v klidu analyzovat zastavený program * Musíme si vystačit s textem v teminálu #### Triky Pokud v f-stringu napíšeme za výraz `=` vypíše se i název proměnné: ```python >>> a = 10 >>> b = 9, >>> print(f"{a=}, {b=}") a=10, b=(9,) ``` Knihovna `pprint` nám pythoní struktury vytiskne hezky čitelně. ```python >>> from pprint import pprint >>> x = [{"x": 42, "y": [1,2,8], "z": {"z": "hi"*20}}, 2024, "Ha"*20] >>> pprint(x) [{'x': 42, 'y': [1, 2, 8], 'z': {'z': 'hihihihihihihihihihihihihihihihihihihihi'}}, 2024, 'HaHaHaHaHaHaHaHaHaHaHaHaHaHaHaHaHaHaHaHa'] ``` #### Logování Na pomezí hledání chyb a dobré praxe. Dobré logovací výpisy ulehčují následné lazení. ```python >>> import logging >>> logging.basicConfig(level=logging.INFO) >>> logger = logging.getLogger(__name__) >>> logger.info("Program nastartován") INFO:__main__:Program nastartován >>> logger.debug("Porgram stále běží...") >>> logger.error("Ajajaj!") ERROR:__main__:Ajajaj! ``` #### Metoda `__repr__` a `__str__` Objekty v pythonu se v základu vypisují dosti ošklivě a neinformativně: ```python >>> class Auto: ... def __init__(self, barva, rok_vyroby): ... self.barva = barva ... self.rok_vyroby = rok_vyroby ... >>> Auto("cervena", 1997) <__main__.Auto object at 0x74f76ffbee10> ``` Pokud definujeme `__repr__`, tak bude výpis hezčí a informativnější: ```python >>> class Auto: ... def __init__(self, barva, rok_vyroby): ... self.barva = barva ... self.rok_vyroby = rok_vyroby ... ... def __repr__(self): ... return f"Auto(barva='{self.barva}', rok_vyroby={self.rok_vyroby})" ... >>> Auto("cervena", 1997) Auto(barva='cervena', rok_vyroby=1997) ``` #### Atribut `__dict__` Instance tříd (ale i třídy) mají také atribut `__dict__`: ```python >>> class Auto: ... pocet_kol = 4 ... ... def __init__(self, barva, rok_vyroby): ... self.barva = barva ... self.rok_vyroby = rok_vyroby ... >>> Auto("cervena", 1997).__dict__ {'barva': 'cervena', 'rok_vyroby': 1997} >>> Auto.__dict__ mappingproxy({'__module__': '__main__', 'pocet_kol': 4, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}) ``` Něco podobného dělá i `dir()`, funguje více formou "nejlepší snahy". #### Dicty `locals()` a `globals()` Tato volání vrátí dicty všech lokálních a globálních proměnných. ### Typing Typing velmi zlepšuje čitelnost kódu. Pokud máme v kódu chybu a sami se v tom trochu ztrácíme, tak není od věci si k proměnným datové typy napsat. Občas nám potom i samotné IDE napoví, kde bude chyba. Typing typicky nemá žádný vliv na provádění kódu. ### Asserty Asserty "zjišťují" co by mělo v daný okamžik platit. ```python >>> def vypis_vek(vek: int): ... assert vek > 0 ... print("Je mi", vek, "let.") ... >>> vypis_vek(-2) Traceback (most recent call last): File "", line 1, in File "", line 2, in vypis_vek AssertionError ``` * Program musí fungovat i bez assertů * Nepoužívat k testování vstupů * Můžou zlepšit čitelnost programu * Můžou pomoci IDE s porozuměním kódu ### Testy * Pomůžou nám lépe ohraničit chybu * Hned uvidím, co naše oprava udělala s tím, jak kód funguje (nerozbili jsme si to jinde?) * Budeme mít jistotu, že se nám do kódu podobná chyba už nevrátí * Může vytvořit dobrý vstupní bod pro debugger (pro vyvolání chyby nemusíme pouštět celý program a dostávat se do daného místa) * Může nás ujistit, že chyba v testované části kódu není a máme hledat jinde * Tvorba testů nás může donutit kód strukturovat lépe, aby se dal testovat -> během toho tu chybu odhalíme ### Možnost pokročilejší -- debugger Co je debugger? Program umožňující spustit váš program ve speciálním režimu: * Můžete mu říct "Teď stůj" a on se zapauzuje * Toto mu můžete říci zejména pro nějaký konkrétní řádek * Můžete se ptát na stav programu, když program stojí * kde zrovna v kódu je * jaký je obsah proměnných * Můžete mu říct, aby v programu popostoupil jen o jeden řádek, nebo došel na jiné vhodné místo **Výhody** * do všeho lépe vidíme * můžeme program zastavit a v klidu analyzovat onen okamžik * IDE nám vše čitelněji zobrazí **Nevýhody** * Chvíli trvá vše připravit (IDE nám často pomůže) #### Základní pythoní debugger - `pdb` Zastavení na povel: ``` breakpoint() ``` Ve starších pythonech (před 3.7): ``` import pdb; pdb.set_trace() ``` Na tomto řádku se program zastaví `python -m pdb program.py` Užitečné příkazy: * `?` zobrazí nápovědu * `continue` opět rozběhne program * `list` vypíše kde v programu je * `display x` Začne zobrazovat proměnnou `x` * `next` udělá jeden krok kupředu * `step` udělá jeden krok dovnitř * `tb` zobrazí trace * `interact` pustí na daném místě interaktivní python * `up` popostoupí ve stacku výše (k volající funkci) * `Ctrl-C` zastaví běžící program ... ale tohle není vůbec pohodlné! Proto tu máme IDE, ve kterém to jde lépe. #### Co je "frame" a jak se vlastně volají funkce Když se zavolá funkce, tak se pro to jedno konkrétní volání uloží do paměti všechny její lokální poroměnné. Jelikož funkce můžou volat další funkce (a to i sebe), tak je v paměti počítače v každou chvíli uložen seznam (posloupnost) volaných funkcí a jejích parametrů -- rámců. ## Když už nic nefunguje * odpočiňte si * jděte na jiný úkol * projděte se, uvařte si kávu, uspokojte jiné lidské potřeby * zkuste několik minut o problému jen přemýšlet a nedotýkejte se počítače * zkuste program upravovat tak, aby v něm zůstala pouze část, která vykazuje chybu, odstaňte všechen jiný kód, co jde * **někomu problém vysvětlete vlastními slovy** a vůbec nevadí, že dotyčný nebude chápat o čem mluvíte ... klidně to může být gumová kačenka!