2.41. Depanare¶
Majoritatea scripturilor care eșuează pe cameră eșuează într-unul din trei moduri: aruncă o excepție, produc o valoare greșită sau se blochează. Fiecare are propriul set de instrumente.
2.41.1. Citirea unui traceback¶
Când un script aruncă o excepție și nimic nu o tratează, REPL sau IDE-ul tipărește un traceback – o înregistrare a lanțului de apeluri, de la scriptul cel mai exterior până la linia care a aruncat excepția.
Un traceback se citește de jos în sus:
Linia de jos numește clasa excepției și mesajul acesteia (
ValueError: invalid literal for int()...).Fiecare bloc
File "...", line N, in <name>aflat deasupra ei este un cadru – cu un apel mai adânc pe măsură ce urci.Cadrul de sus de tot este locul unde a pornit scriptul; cadrul de jos de tot este locul unde s-a declanșat eroarea.
Citește mai întâi partea de jos pentru a afla ce a mers prost, apoi parcurge în sus pentru a vedea cum a ajuns scriptul acolo. Numerele de linie indică locațiile exacte din codul sursă al scriptului.
2.41.2. Depanare prin afișare (print)¶
Cel mai rapid mod de a afla ce face un script este să tipărești valorile suspecte. Trei funcții încorporate fac afișările mai utile:
repr()– returnează șirul în stil dezvoltator pentru o valoare.print(repr(value))distinge"5"de5șiNonede"None", lucru pe care un simpluprint()nu îl poate face.type()– returnează clasa unei valori.print(type(value))este modul prin care afli dacă variabila care „ar trebui să fie un int” este, pe ascuns, un șir de caractere.len()– lungimea unei secvențe sau colecții. O fracțiune surprinzător de mare a erorilor sunt probleme de tip off-by-one sau de nepotrivire a dimensiunilor.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Plasează câte un print în fiecare ramură care te interesează – ambele brațe ale unui if, fiecare bloc except, corpul unei bucle pe care o suspectezi că rulează de zero ori. Costul este o linie de ieșire; valoarea este aflarea dacă traseul de cod pe care crezi că rulează este într-adevăr cel care rulează.
2.41.3. Explorarea unui obiect¶
Două funcții încorporate răspund la întrebarea „ce pot face cu acest lucru”:
dir()– returnează o listă cu fiecare nume definit pe un obiect: metode, atribute, metode dunder, tot.help()– tipărește docstring-ul (și, pe CPython, semnătura) unei funcții, metode sau clase.
Folosește-le împreună: dir găsește numele, help explică ce face acesta.
2.41.3.1. Găsirea unui nume cu dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
Prima parte a listei conține metode dunder, moștenite de la fiecare obiect; numele care merită căutate sunt de obicei după ele. dir funcționează pe orice – o clasă, o instanță, un modul, un tip încorporat:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
A doua formă este modul prin care afli care nume de nivel superior expune de fapt un modul, fără a părăsi REPL.
2.41.3.2. Căutarea cu help¶
Odată ce dir a scos la suprafață un candidat, help îl descrie:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
Pe MicroPython, help este mai sărac decât pe CPython – uneori doar semnătura, alteori un docstring de o linie, alteori nimic pentru funcțiile C încorporate. Rămâne totuși un memento rapid când tooltip-ul din IDE nu este la îndemână.
2.41.4. Când ceva se blochează¶
Un script care nu se mai întoarce este mai greu de diagnosticat decât unul care aruncă o excepție. Vinovați obișnuiți:
O buclă
whilea cărei condiție nu devine niciodată falsă. Adaugă un print al variabilei buclei la fiecare iterație; dacă valoarea nu se schimbă, corpul buclei are o eroare.Un apel blocant care așteaptă o intrare ce nu mai sosește niciodată – o citire dintr-o coadă goală, un sleep fără sfârșit. Încadrează apelul cu print-uri pentru a vedea pe ce linie s-a blocat scriptul.
O recursivitate infinită. Traceback-ul generat când se declanșează în cele din urmă (cu
RecursionError) indică de obicei exact spre ea.
Cea mai eficientă metodă de recuperare a unui script blocat este butonul stop din IDE, care trimite o KeyboardInterrupt către script prin USB. Întreruperea apare ca un traceback la linia care rulează în acel moment – adesea exact linia care nu se mai întoarce.
Notă
Dacă un blocaj rezistă oricărui diagnostic – scriptul pare corect, traceback-ul întreruperii indică spre o funcție încorporată sau spre cod de firmware în loc de scriptul tău, sau același cod a funcționat pe o versiune anterioară de firmware – cauza poate fi o eroare de firmware, nu o eroare de script. Redu scriptul la cel mai mic reproducător care încă se blochează și deschide un raport pe forumul OpenMV. Include versiunea de firmware, placa pe care a rulat și scriptul redus.
2.41.5. Scoate diagnosticele înainte de livrare¶
Print-urile strategice din timpul dezvoltării sunt excelente; o sută de apeluri print lăsate într-un script de producție aglomerează ieșirea și folosesc heap pe care munca reală l-ar putea folosi în schimb. Când o eroare este reparată, scoate print-urile (sau protejează-le în spatele unui indicator de depanare pe care îl poți dezactiva).
Pentru diagnostice care ar trebui să rămână în traseul de cod pe termen lung, treci de la print() la modulul logging. Acesta atașează un nivel fiecărui mesaj (debug, info, warning, error) și permite ca o singură setare să reducă la tăcere mesajele neimportante în producție:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Setarea nivelului logger-ului la logging.WARNING face ca apelurile info și debug să nu coste practic nimic (șirul mesajului nu este construit niciodată), fără a fi nevoie să comentezi liniile. Aceasta face din logging instrumentul potrivit pentru diagnostice permanente; un simplu print este bun pentru cele de unică folosință.