2.41. ניפוי שגיאות¶
רוב הסקריפטים שנכשלים על המצלמה נכשלים באחת משלוש דרכים: הם מעלים חריגה, הם מפיקים ערך שגוי, או שהם נתקעים. לכל אחת מהן יש קבוצת כלים שונה.
2.41.1. קריאת traceback¶
כאשר סקריפט מעלה חריגה ושום דבר אינו מטפל בה, ה-REPL או ה-IDE מדפיסים traceback – רישום של שרשרת הקריאות מהסקריפט החיצוני ביותר עד אל השורה שהעלתה את החריגה.
קוראים traceback מלמטה למעלה:
השורה התחתונה מציינת את מחלקת החריגה ואת הודעתה (
ValueError: invalid literal for int()...).כל בלוק
File "...", line N, in <name>שמעליה הוא פריים – קריאה אחת עמוק יותר ככל שעולים.הפריים העליון ביותר הוא המקום שבו הסקריפט התחיל; הפריים התחתון ביותר הוא המקום שבו השגיאה התרחשה.
קראו תחילה את התחתית כדי ללמוד מה השתבש, ואז התקדמו כלפי מעלה כדי לראות כיצד הגיע הסקריפט לשם. מספרי השורות מצביעים על מיקומי מקור מדויקים בסקריפט.
2.41.2. ניפוי באמצעות הדפסות¶
הדרך המהירה ביותר לגלות מה סקריפט עושה היא להדפיס את הערכים החשודים. שלוש פונקציות מובנות הופכות הדפסות לשימושיות יותר:
repr()– מחזירה את מחרוזת הסגנון של המפתח עבור ערך.print(repr(value))מבדילה בין"5"ל-5וביןNoneל-"None", מה ש-print()רגילה אינה יכולה לעשות.type()– מחזירה את המחלקה של ערך.print(type(value))היא הדרך לגלות אם המשתנה ש“אמור להיות int“ הוא בעצם מחרוזת.len()– האורך של רצף או אוסף. שיעור מפתיע של באגים נובעים מטעויות של אחד או מאי-התאמה בגודל.
print("got:", repr(value), "type:", type(value), "len:", len(value))
שתלו הדפסה בתוך כל ענף שחשוב לכם – בשני זרועות של if, בכל בלוק except, בגוף לולאה שאתם חושדים שרצה אפס פעמים. העלות היא שורת פלט; הערך הוא לגלות אם נתיב הקוד שאתם חושבים שרץ הוא זה שבאמת רץ.
2.41.3. חקירת אובייקט¶
שתי פונקציות מובנות עונות על השאלה ”מה אני יכול לעשות עם הדבר הזה“:
dir()– מחזירה רשימה של כל שם המוגדר על אובייקט: מתודות, תכונות, dunders, הכול.help()– מדפיסה את ה-docstring (ועל CPython, את החתימה) של פונקציה, מתודה או מחלקה.
השתמשו בהן יחד: dir מאתרת את השם, help מסבירה מה הוא עושה.
2.41.3.1. מציאת שם באמצעות dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
החלק הראשון של הרשימה הוא מתודות dunder, שעוברות בירושה מכל אובייקט; השמות שכדאי לסרוק עבורם נמצאים בדרך כלל אחריהם. dir עובדת על כל דבר – מחלקה, מופע, מודול, טיפוס מובנה:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
הצורה השנייה הזו היא הדרך לגלות אילו שמות ברמה העליונה מודול באמת חושף מבלי לעזוב את ה-REPL.
2.41.3.2. חיפוש באמצעות help¶
ברגע ש-dir חשפה מועמד, help מתארת אותו:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
ב-MicroPython, help רזה יותר מאשר ב-CPython – לפעמים רק החתימה, לפעמים docstring של שורה אחת, לפעמים שום דבר עבור פונקציות C מובנות. עדיין מדובר בתזכורת מהירה כאשר הטולטיפ של ה-IDE אינו בהישג יד.
2.41.4. כאשר משהו נתקע¶
סקריפט שאינו חוזר קשה יותר לאבחון מאשר אחד שמעלה חריגה. אשמים נפוצים:
לולאת
whileשתנאה לעולם אינו הופך לשקרי. הוסיפו הדפסה של משתנה הלולאה בכל איטרציה; אם הערך אינו משתנה, יש באג בגוף הלולאה.קריאה חוסמת הממתינה לקלט שלעולם אינו מגיע – קריאה מתור ריק, sleep ללא סוף. הקיפו את הקריאה בהדפסות כדי לראות באיזו שורה הסקריפט נתקע.
רקורסיה אינסופית. ה-traceback כשהיא מתרחשת בסופו של דבר (עם
RecursionError) מצביע בדרך כלל ישירות עליה.
ההתאוששות היעילה ביותר עבור סקריפט תקוע היא כפתור ה-stop של ה-IDE, ששולח KeyboardInterrupt לסקריפט דרך USB. הפסיקה צפה כ-traceback בשורה הרצה כעת – לעיתים קרובות בדיוק השורה שאינה חוזרת.
הערה
אם תקיעה מתנגדת לכל אבחון – הסקריפט נראה תקין, ה-traceback של הפסיקה מצביע אל תוך פונקציה מובנית או אל תוך קוד קושחה ולא אל הסקריפט שלכם, או שאותו קוד עבד על גרסת קושחה קודמת – ייתכן שהסיבה היא באג בקושחה ולא באג בסקריפט. צמצמו את הסקריפט למשחזר הקטן ביותר שעדיין נתקע ופתחו דיווח ב-פורום OpenMV. כללו את גרסת הקושחה, את הלוח שעליו הוא רץ, ואת הסקריפט המצומצם.
2.41.5. הסירו את האבחונים לפני המסירה¶
הדפסות אסטרטגיות במהלך הפיתוח מצוינות; מאה קריאות print שנשארו בסקריפט ייצור מבלגנות את הפלט ומשתמשות בערימה (heap) שהעבודה האמיתית הייתה יכולה להשתמש בה במקום. כאשר באג מתוקן, הסירו את ההדפסות (או שמרו אותן מאחורי דגל ניפוי שתוכלו לכבות).
עבור אבחונים שאמורים להישאר בנתיב הקוד לטווח ארוך, עברו מ-print() למודול logging. הוא מצרף רמה לכל הודעה (debug, info, warning, error) ומאפשר להגדרה אחת להשתיק את השקטות בייצור:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
קביעת רמת ה-logger ל-logging.WARNING הופכת את קריאות ה-info וה-debug לעולות כמעט כלום (מחרוזת ההודעה לעולם אינה נבנית), מבלי שיהיה צורך להפוך שורות להערות. זה הופך את logging לכלי הנכון עבור אבחונים קבועים; print גולמי מתאים עבור חד-פעמיים.