2.41. Felsökning¶
De flesta skript som misslyckas på kameran gör det på ett av tre sätt: de kastar ett undantag, de ger fel värde, eller de hänger sig. Vart och ett har sin egen uppsättning verktyg.
2.41.1. Läsa en traceback¶
När ett skript kastar ett undantag och ingenting hanterar det skriver REPL eller IDE:n ut en traceback – en redogörelse för anropskedjan från det yttersta skriptet ner till den rad som kastade undantaget.
En traceback läses nerifrån och upp:
Den nedersta raden anger undantagsklassen och dess meddelande (
ValueError: invalid literal for int()...).Varje block
File "...", line N, in <name>ovanför den är en frame – ett anrop djupare för varje steg uppåt.Den allra översta framen är där skriptet startade; den allra nedersta framen är där felet uppstod.
Läs den nedersta först för att förstå vad som gick fel, gå sedan uppåt för att se hur skriptet hamnade där. Radnumren pekar på exakta källkodsplatser i skriptet.
2.41.2. Felsökning med utskrifter¶
Det snabbaste sättet att ta reda på vad ett skript gör är att skriva ut de misstänkta värdena. Tre inbyggda funktioner gör utskrifter mer användbara:
repr()– returnerar utvecklarstilssträngen för ett värde.print(repr(value))skiljer"5"från5ochNonefrån"None", vilket ett vanligtprint()inte kan.type()– returnerar klassen för ett värde.print(type(value))är sättet att ta reda på om variabeln som ”borde vara en int” i hemlighet är en sträng.len()– längden på en sekvens eller samling. En förvånansvärt stor del av buggarna är off-by-one- eller storleksfel.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Lägg in en utskrift i varje gren du bryr dig om – båda armarna i ett if, varje except-block, kroppen i en loop som du misstänker körs noll gånger. Kostnaden är en rad utdata; värdet är att få veta om den kodväg du tror körs är den som faktiskt körs.
2.41.3. Utforska ett objekt¶
Två inbyggda funktioner besvarar ”vad kan jag göra med det här”:
dir()– returnerar en lista över alla namn som är definierade på ett objekt: metoder, attribut, dunders, alltihop.help()– skriver ut docstringen (och på CPython, signaturen) för en funktion, metod eller klass.
Använd dem tillsammans: dir hittar namnet, help förklarar vad det gör.
2.41.3.1. Hitta ett namn med dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
Den första delen av listan är dunder-metoder, ärvda från varje objekt; namnen värda att leta efter kommer vanligtvis efter dem. dir fungerar på allt – en klass, en instans, en modul, en inbyggd typ:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
Den andra formen är sättet att ta reda på vilka namn på toppnivå en modul faktiskt exponerar utan att lämna REPL:en.
2.41.3.2. Slå upp det med help¶
När dir har lyft fram en kandidat beskriver help den:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
På MicroPython är help magrare än på CPython – ibland bara signaturen, ibland en docstring på en rad, ibland ingenting alls för inbyggda C-funktioner. Det är ändå en snabb påminnelse när IDE:ns verktygstips inte finns till hands.
2.41.4. När något hänger sig¶
Ett skript som inte returnerar är svårare att diagnostisera än ett som kastar ett undantag. Vanliga orsaker:
En
while-loop vars villkor aldrig blir falskt. Lägg till en utskrift av loopvariabeln vid varje iteration; om värdet inte ändras har loopkroppen en bugg.Ett blockerande anrop som väntar på indata som aldrig kommer – en läsning från en tom kö, en vila utan slut. Omge anropet med utskrifter för att se vilken rad skriptet har fastnat på.
En oändlig rekursion. Tracebacken som så småningom uppstår (med
RecursionError) pekar vanligtvis direkt på den.
Det effektivaste sättet att återhämta sig från ett skript som hängt sig är IDE:ns stop-knapp, som skickar ett KeyboardInterrupt till skriptet över USB. Avbrottet visar sig som en traceback vid den rad som körs för tillfället – ofta exakt den rad som inte returnerar.
Anteckning
Om en hängning motstår all diagnostik – skriptet verkar korrekt, avbrottets traceback pekar in i en inbyggd funktion eller in i firmwarekod snarare än i ditt skript, eller samma kod fungerade på en tidigare firmwarebygge – kan orsaken vara en firmwarebugg snarare än en skriptbugg. Reducera skriptet till den minsta reproducer som fortfarande hänger sig och öppna en rapport på OpenMV-forumet. Inkludera firmwareversionen, kortet som det kördes på och det reducerade skriptet.
2.41.5. Ta bort diagnostiken innan leverans¶
Strategiska utskrifter under utveckling är utmärkta; hundra print-anrop kvarlämnade i ett produktionsskript skräpar ner utdata och använder heap som det riktiga arbetet skulle kunna använda istället. När en bugg är åtgärdad, ta bort utskrifterna (eller skydda dem bakom en debugflagga som du kan slå av).
För diagnostik som ska finnas kvar i kodvägen långsiktigt, byt från print() till modulen logging. Den knyter en nivå till varje meddelande (debug, info, warning, error) och låter en enda inställning tysta de tysta i produktion:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Att sätta loggerns nivå till logging.WARNING gör att info- och debug-anropen i praktiken inte kostar någonting (meddelandesträngen byggs aldrig), utan att man behöver kommentera bort rader. Det gör logging till rätt verktyg för permanent diagnostik; rå print duger bra för engångsdiagnostik.