2.41. Debuggen¶
De meeste scripts die op de camera falen, falen op een van drie manieren: ze veroorzaken een exception, ze produceren de verkeerde waarde, of ze blijven hangen. Voor elk geval is er een andere set hulpmiddelen.
2.41.1. Een traceback lezen¶
Wanneer een script een exception veroorzaakt en niets de exception afhandelt, drukt de REPL of de IDE een traceback af – een verslag van de aanroepketen vanaf het buitenste script tot aan de regel die de exception veroorzaakte.
Een traceback lees je van onder naar boven:
De onderste regel noemt de exception-klasse en de bijbehorende boodschap (
ValueError: invalid literal for int()...).Elk
File "...", line N, in <name>-blok daarboven is een frame – één aanroep dieper naarmate je omhoog gaat.Het allerbovenste frame is waar het script begon; het alleronderste frame is waar de fout afging.
Lees eerst de onderkant om te weten te komen wat er misging, loop daarna omhoog om te zien hoe het script daar terechtkwam. De regelnummers wijzen naar exacte broncodelocaties in het script.
2.41.2. Debuggen met print¶
De snelste manier om uit te vinden wat een script doet, is de verdachte waarden af te drukken. Drie built-ins maken prints nuttiger:
repr()– geeft de developer-stijl string voor een waarde terug.print(repr(value))onderscheidt"5"van5enNonevan"None", wat een gewoneprint()niet kan.type()– geeft de klasse van een waarde terug.print(type(value))is de manier om uit te vinden of de variabele die “een int zou moeten zijn” stiekem een string is.len()– de lengte van een sequentie of collectie. Een verrassend groot deel van de bugs zijn off-by-one- of size-mismatch-problemen.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Plaats een print in elke tak die je interesseert – beide armen van een if, elk except-blok, de body van een lus waarvan je vermoedt dat hij nul keer draait. De kost is een regel uitvoer; de waarde is uitvinden of het codepad dat je denkt dat draait, ook werkelijk draait.
2.41.3. Een object verkennen¶
Twee built-ins beantwoorden de vraag “wat kan ik met dit ding doen”:
dir()– geeft een lijst van elke naam die op een object gedefinieerd is: methoden, attributen, dunders, de hele mikmak.help()– drukt de docstring (en op CPython, de signatuur) van een functie, methode of klasse af.
Gebruik ze samen: dir vindt de naam, help legt uit wat hij doet.
2.41.3.1. Een naam vinden met dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
Het eerste deel van de lijst bestaat uit dunder-methoden, geërfd van elk object; de namen die het scannen waard zijn, staan er meestal na. dir werkt op alles – een klasse, een instantie, een module, een built-in type:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
Die tweede vorm is de manier om uit te vinden welke top-level namen een module daadwerkelijk blootstelt zonder de REPL te verlaten.
2.41.3.2. Het opzoeken met help¶
Zodra dir een kandidaat aan het licht heeft gebracht, beschrijft help hem:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
Op MicroPython is help magerder dan op CPython – soms alleen de signatuur, soms een docstring van één regel, soms niets voor built-in C-functies. Het blijft een snelle geheugensteun wanneer de tooltip van de IDE niet binnen handbereik is.
2.41.4. Wanneer iets blijft hangen¶
Een script dat niet terugkeert is moeilijker te diagnosticeren dan een script dat een exception veroorzaakt. Veelvoorkomende boosdoeners:
Een
while-lus waarvan de conditie nooit onwaar wordt. Voeg een print van de lusvariabele bij elke iteratie toe; als de waarde niet verandert, zit er een bug in de body van de lus.Een blokkerende aanroep die wacht op invoer die nooit komt – een read van een lege wachtrij, een sleep zonder einde. Omring de aanroep met prints om te zien op welke regel het script vastloopt.
Een oneindige recursie. De traceback wanneer hij uiteindelijk afgaat (met
RecursionError) wijst er meestal recht naar.
De meest effectieve manier om een vastgelopen script te herstellen is de stop-knop van de IDE, die een KeyboardInterrupt naar het script stuurt via USB. De interrupt verschijnt als een traceback op de regel die op dat moment draait – vaak precies de regel die niet terugkeert.
Notitie
Als een hang elke diagnose weerstaat – het script lijkt correct, de interrupt-traceback wijst naar een built-in of naar firmware-code in plaats van naar jouw script, of dezelfde code werkte op een eerdere firmware-build – dan kan de oorzaak een firmwarebug zijn in plaats van een scriptbug. Reduceer het script tot de kleinste reproductie die nog steeds blijft hangen en open een melding op het OpenMV-forum. Vermeld de firmwareversie, het board waarop het draaide en het gereduceerde script.
2.41.5. Verwijder de diagnostiek voordat je in productie gaat¶
Strategische prints tijdens de ontwikkeling zijn geweldig; honderd print-aanroepen die in een productiescript blijven staan, vervuilen de uitvoer en gebruiken heap die het echte werk had kunnen gebruiken. Wanneer een bug is opgelost, verwijder je de prints (of plaats je ze achter een debug-vlag die je kunt uitschakelen).
Voor diagnostiek die op de lange termijn in het codepad moet blijven, schakel je over van print() naar de logging-module. Die koppelt een niveau aan elke boodschap (debug, info, warning, error) en laat één enkele instelling de stille berichten in productie het zwijgen opleggen:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Door het niveau van de logger op logging.WARNING te zetten, kosten de info- en debug-aanroepen vrijwel niets (de boodschapstring wordt nooit opgebouwd), zonder dat je regels hoeft uit te commentariëren. Dat maakt logging het juiste hulpmiddel voor permanente diagnostiek; ruwe print is prima voor wegwerp-diagnostiek.