2.41. Ladění#

Většina skriptů, které na kameře selžou, selže jedním ze tří způsobů: vyvolají výjimku, vyprodukují nesprávnou hodnotu, nebo se zaseknou. Každý z nich vyžaduje jinou sadu nástrojů.

2.41.1. Čtení tracebacku#

Když skript vyvolá výjimku a nic ji neobslouží, REPL nebo IDE vypíše traceback – záznam řetězce volání od nejvzdálenějšího skriptu až po řádek, který výjimku vyvolal.

Traceback se čte zdola nahoru:

  • Spodní řádek pojmenovává třídu výjimky a její zprávu (ValueError: invalid literal for int()...).

  • Každý blok File "...", line N, in <name> nad ním je snímek (frame) – o jedno volání hlouběji, jak postupujete nahoru.

  • Úplně nejvyšší snímek je místo, kde skript začal; úplně nejnižší snímek je místo, kde chyba nastala.

Nejprve si přečtěte spodní část, abyste zjistili, co se pokazilo, a poté postupujte nahoru, abyste viděli, jak se skript do daného místa dostal. Čísla řádků ukazují na přesná místa ve zdrojovém kódu skriptu.

2.41.3. Zkoumání objektu#

Dvě vestavěné funkce odpovídají na otázku „co s touto věcí mohu dělat“:

  • dir() – vrací seznam každého jména definovaného na objektu: metody, atributy, dunder metody, vše.

  • help() – vypíše dokumentační řetězec (a na CPythonu i signaturu) funkce, metody nebo třídy.

Používejte je společně: dir najde jméno, help vysvětlí, co dělá.

2.41.3.1. Nalezení jména pomocí dir#

>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
 '__eq__', '__ge__', ..., 'append', 'clear', 'copy',
 'count', 'extend', 'index', 'insert', 'pop', 'remove',
 'reverse', 'sort']

První část seznamu tvoří dunder metody zděděné z každého objektu; jména, která stojí za prohlédnutí, jsou obvykle až za nimi. dir funguje na čemkoli – na třídě, instanci, modulu, vestavěném typu:

>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']

Tato druhá forma je způsob, jak zjistit, která jména nejvyšší úrovně modul skutečně zpřístupňuje, aniž byste opustili REPL.

2.41.3.2. Vyhledání pomocí help#

Jakmile dir odhalí kandidáta, help jej popíše:

>>> help(str.split)
split(sep=None, maxsplit=-1)
    Return a list of the words in the string, ...

Na MicroPythonu je help úspornější než na CPythonu – někdy jen signatura, někdy jednořádkový dokumentační řetězec, někdy pro vestavěné funkce v C nic. Přesto je to rychlá připomínka, když není po ruce nápověda v IDE.

2.41.4. Když se něco zasekne#

Skript, který se nevrací, se diagnostikuje obtížněji než ten, který vyvolá výjimku. Časté příčiny:

  • Cyklus while, jehož podmínka se nikdy nestane nepravdivou. Přidejte výpis řídicí proměnné cyklu při každé iteraci; pokud se hodnota nemění, tělo cyklu obsahuje chybu.

  • Blokující volání čekající na vstup, který nikdy nedorazí – čtení z prázdné fronty, spánek bez konce. Obklopte volání výpisy, abyste viděli, na kterém řádku skript uvázl.

  • Nekonečná rekurze. Traceback, který se nakonec objeví (s RecursionError), na ni obvykle ukazuje přímo.

Nejúčinnějším způsobem obnovy zaseknutého skriptu je tlačítko stop v IDE, které skriptu pošle přes USB KeyboardInterrupt. Přerušení se objeví jako traceback na právě běžícím řádku – často právě na tom řádku, který se nevrací.

Poznámka

Pokud se zaseknutí brání každé diagnostice – skript se zdá správný, traceback přerušení ukazuje do vestavěné funkce nebo do kódu firmwaru, a ne do vašeho skriptu, nebo tentýž kód fungoval na předchozím sestavení firmwaru – příčinou může být chyba firmwaru spíše než chyba skriptu. Zredukujte skript na nejmenší reprodukovatelný příklad, který se stále zasekává, a založte hlášení na fóru OpenMV. Uveďte verzi firmwaru, desku, na které běžel, a zredukovaný skript.

2.41.5. Před nasazením diagnostiku odstraňte#

Strategické výpisy během vývoje jsou skvělé; sto volání print ponechaných v produkčním skriptu zahlcuje výstup a spotřebovává haldu (heap), kterou by mohla využívat skutečná práce. Když je chyba opravena, výpisy odstraňte (nebo je ohraničte ladicím příznakem, který lze vypnout).

Pro diagnostiku, která by měla zůstat v cestě kódu dlouhodobě, přejděte z print() na modul logging. Ten ke každé zprávě připojí úroveň (debug, info, warning, error) a umožňuje jediným nastavením v produkci umlčet ty méně důležité:

import logging

log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")

Nastavení úrovně loggeru na logging.WARNING způsobí, že volání info a debug nestojí prakticky nic (řetězec zprávy se nikdy nesestaví), aniž byste museli zakomentovávat řádky. To dělá z logging správný nástroj pro trvalou diagnostiku; surový print je v pořádku pro tu jednorázovou.