שגיאות הן הדרך של Python לספר מה קרה
כל שגיאה ב-Python היא אובייקט עם סוג, הודעה ו-traceback: שרשרת הקריאות שהובילה אליה. לדעת לקרוא שגיאה היטב זו מיומנות הדיבוג החשובה ביותר שאפשר לרכוש. העמוד הזה הוא סיור בשגיאות שבאמת תפגשו, ובהרגלים שעוזרים לתקן אותן מהר.
למבט מעמיק יותר על המנגנון של try/except ועל זריקת שגיאות משלכם, עמוד החריגות מכסה את התחביר. העמוד הזה עוסק בשגיאות הספציפיות ובאיך לקרוא אותן.
קריאת traceback
הריצו משהו שנשבר:
def divide(a, b):
return a / b
def report(values):
for v in values:
print(divide(10, v))
report([5, 2, 0])
Python מדפיסה משהו כזה:
Traceback (most recent call last):
File "script.py", line 8, in <module>
report([5, 2, 0])
File "script.py", line 6, in report
print(divide(10, v))
File "script.py", line 2, in divide
return a / b
ZeroDivisionError: division by zero
קראו את זה מלמטה למעלה:
ZeroDivisionError: division by zero: סוג החריגה וההודעה. זה מה שהשתבש.return a / bב-divide, שורה 2: השורה שבאמת זרקה את החריגה.print(divide(10, v))ב-report, שורה 6: הקריאה שגרמה לזה.report([5, 2, 0])ברמת המודול, שורה 8: המקום שבו הכל התחיל.
המסגרת התחתונה היא כמעט תמיד המקום של התיקון. כשספרייה זורקת שגיאה עמוק בתוך הקוד הפנימי שלה, עלו במעלה ה-traceback עד המסגרת הראשונה ב_קוד שלכם_: זו הקריאה שהעברתם בה קלט שגוי.
השגיאות שתפגשו הכי הרבה
NameError
"Name 'mesage' is not defined." נגרמת משגיאות כתיב או משימוש במשתנה לפני שהוצב בו ערך. בגרסאות עדכניות, הודעת השגיאה של Python בדרך כלל מציעה תחליף סביר ("Did you mean 'message'?").
תיקון: בדקו את האיות ואת הטווח (scope). אם השם קיים רק בתוך פונקציה, אי אפשר לקרוא אותו מבחוץ.
TypeError
"Can only concatenate str (not 'int') to str." טיפוס שגוי לפעולה. הקלאסיקות: חיבור מחרוזת למספר, קריאה למשהו שאינו ניתן לקריאה, העברת מספר שגוי של ארגומנטים לפונקציה.
תיקון: המירו טיפוסים במפורש (str(30), int("30")) או בדקו מה אתם באמת מעבירים. f-string בדרך כלל קריא יותר משרשור עם + בין טיפוסים שונים: f"age: {30}".
ValueError
"Invalid literal for int() with base 10: 'hello'." הטיפוס נכון (int() מקבלת מחרוזת), אבל ה_ערך_ לא מתאים. נפוצה עם int(), float(), פענוח תאריכים, ופונקציות שמקבלות ארגומנט בטווח מוגבל.
תיקון: אמתו לפני ההמרה, או תפסו את השגיאה וטפלו בה:
KeyError
"KeyError: 'charlie'." המפתח לא נמצא ב-dict. שלושה תיקונים אידיומטיים, לפי הכוונה:
ל-dict שבו מפתחות חסרים צריכים להיווצר אוטומטית, כדאי להכיר את collections.defaultdict.
IndexError
"List index out of range." ביקשתם מיקום שלא קיים ברצף.
תיקון: הגנו עם בדיקת אורך, השתמשו ב--1 כדי לפנות לאיבר האחרון, או השתמשו בחיתוך (numbers[5:6] מחזיר [] במקום לזרוק שגיאה).
AttributeError
"'NoneType' object has no attribute 'upper'." קראתם למתודה על משהו שאין לו אותה. כמעט תמיד זה אומר שמשתנה הוא מטיפוס לא צפוי, הרבה פעמים None כשציפיתם לערך אמיתי.
תיקון: בררו מאיפה הגיע ה-None. print(type(var)) או נקודת עצירה ממש לפני השגיאה הם הדרך המהירה ביותר. פונקציות ש"לפעמים נכשלות" מחזירות בדרך כלל None; בדקו את ערך ההחזרה שלהן לפני שקוראים עליו למתודות.
ModuleNotFoundError (ו-ImportError)
import fastapi
"No module named 'fastapi'." החבילה לא מותקנת במפרש ה-Python שהסקריפט שלכם משתמש בו. שתי סיבות נפוצות:
- באמת לא התקנתם אותה. הריצו
python -m pip install fastapiבסביבה הנכונה. - התקנתם אותה, אבל ל-Python אחרת. זה קורה כל הזמן ב-macOS, כש-
pipו-pythonמצביעים על התקנות שונות.
התיקון האמין הוא להתקין עם אותו מפרש שאיתו מריצים:
python -m pip install fastapi
אם אתם משתמשים בסביבה וירטואלית (וכדאי), ודאו שהיא מופעלת גם לפני pip install וגם לפני python script.py.
FileNotFoundError
with open("settings.yaml") as f:
config = f.read()
"[Errno 2] No such file or directory: 'settings.yaml'." הנתיב לא קיים ביחס למקום שבו הסקריפט רץ.
תיקון: הדפיסו os.getcwd() בתחילת הסקריפט כדי לוודא איפה Python מחפשת. השתמשו בנתיבים מוחלטים או ב-pathlib.Path(__file__).parent / "settings.yaml" כדי לעגן נתיבים למיקום של הסקריפט עצמו.
EOFError
name = input("Name: ")
"EOF when reading a line." input() ניסתה לקרוא מ-stdin ולא קיבלה כלום: או שהקלט הועבר בצינור ממקור ריק, או שלחצתם Ctrl-D בשורת הקלט.
תיקון: אם העברה בצינור היא שימוש לגיטימי, עטפו את הקריאה:
try:
name = input("Name: ")
except EOFError:
name = "anonymous"
בעורך שבדפדפן בדפי התיעוד האלה, סביבת ההרצה מדמה קלט, כך שלא תיתקלו בזה שם.
IndentationError ו-SyntaxError
IndentationError: expected an indented block after function definition on line 2
השגיאות האלה מיוחדות: הן מופיעות עוד לפני שהתוכנית רצה. Python סירבה לפענח את הקובץ.
IndentationError: הגוף שלdef,if,forוכדומה חסר או לא מיושר. רוב העורכים מציגים את ההזחה; הפעילו "show whitespace" כדי לתפוס ערבוב של טאבים ורווחים.SyntaxError: שכחתם נקודתיים, סוגריים לא תואמים, או שגיאת כתיב במילה שמורה. גרסאות עדכניות של Python מצביעות עם חץ (^) על התו הבעייתי.
תיקון: הודעת השגיאה מציינת את השורה. לכו להסתכל. אם אין שום בעיה ברורה בשורה הזו, בדקו את השורה שמעליה: סוגריים שלא נסגרו כמה שורות קודם מופיעים הרבה פעמים כשגיאת תחביר הרבה יותר מאוחר.
RuntimeError
שגיאה כללית ל"משהו השתבש בזמן ריצה שאינו אחת מהשגיאות הספציפיות יותר". RecursionError (חריגה מעומק הרקורסיה) היא התמחות נפוצה שלה. קוד של ספריות זורק הרבה פעמים RuntimeError כשהוא במצב לא תקין.
תיקון: קראו את ההודעה; היא בדרך כלל מפורטת. אם הרקורסיה היא הסיבה, המירו אותה ללולאה איטרטיבית, או במקרים נדירים הגדילו את המגבלה עם sys.setrecursionlimit.
דיבוג עם print
לפני שפונים לדיבאגר אמיתי, print(), או עדיף הצורה f"{var=}", תופסת את רוב הבאגים:
f"{var=}" מדפיסה גם את טקסט הביטוי וגם את הערך שלו, כך שלא צריך להקליד שוב את השם במחרוזת הפורמט. הריצו את הסקריפט, עברו על הפלט ומצאו את השורה שבה ערך הפך לשגוי. רוב הבאגים ה"מסתוריים" נעשים ברורים אחרי שלוש או ארבע הדפסות במקומות הנכונים.
נקו את ההדפסות לפני ה-commit. logging.debug(...) נוח יותר מ-print לקוד שעולה לפרודקשן: אפשר להפעיל ולכבות לוגים של דיבוג בלי לערוך שורות.
breakpoint() ו-pdb
כש-print לא מספיקה, breakpoint() מכניסה אתכם לדיבאגר האינטראקטיבי של Python בנקודה הזו:
def discount(price, percent):
breakpoint()
return price * (1 - percent / 100)
discount(100, 20)
הריצו את הסקריפט בטרמינל אמיתי. תגיעו לשורת פקודה (Pdb). כמה פקודות שכדאי להכיר:
p variable: הדפסת משתנה.n: מעבר לשורה הבאה.s: כניסה לתוך קריאה לפונקציה.c: המשך ריצה עד נקודת העצירה הבאה או עד הסוף.q: יציאה.l: הצגת הקוד סביב השורה הנוכחית.
דיבאגרים של IDE (VS Code, PyCharm) עוטפים את אותו פרוטוקול בממשק גרפי: נקודות עצירה שמגדירים בשוליים, וחלונית צד שמציגה משתנים. בחרו במה שמרגיש לכם פחות מסורבל.
הרגלים שמפחיתים את השגיאות שנתקלים בהן
- השתמשו בשמות משתנים תיאוריים. חצי מהרעש של
TypeErrorו-AttributeErrorנעלם כשאי אפשר לשכוח מה יש במשתנה. - אמתו קלט בגבול. פענחו ובדקו קלט משתמש או תוכן קובץ פעם אחת, בתחילת הפונקציה. שאר הקוד יכול אז לסמוך על הערכים.
- היכשלו ברעש, לא בשקט.
except Exception: passחשוף מסתיר בדיוק את השגיאות שאתם צריכים לראות. תפסו חריגות ספציפיות, טפלו בהן במכוון, ותנו לשאר להמשיך הלאה. - קראו קודם את התחתית של ה-traceback. כל דקה שמשקיעים בלמידת קריאת traceback מחזירה את עצמה פי מאה.
רוב השגיאות אינן תעלומות: הן שגיאות כתיב של שורה אחת או טעויות טיפוס עם הודעה ברורה. סמכו על הודעת השגיאה; היא בדרך כלל צודקת.
הגעתם עד כאן
זה סוף חלק העיון. עברתם מ"מה זה Python?" דרך משתנים, בקרת זרימה, אוספים, פונקציות, מחלקות, איטרציה, נתונים מהעולם האמיתי ושגיאות. מכאן הצעדים הבאים הם בצורת פרויקט: בחרו משהו שאתם רוצים לבנות, ועבדו אחורה אל החלקים שצריך ללמוד לעומק. הדפים האלה עדיין יחכו כאן כשתחזרו עם שאלה ספציפית.
שאלות נפוצות
מה זה KeyError ב-Python?
KeyError נזרקת כשמחפשים מפתח שלא קיים ב-dict. users["missing"] זורקת KeyError: 'missing'. החלופות הבטוחות הן users.get("missing", default), users.get("missing") (מחזירה None), או בדיקה מפורשת של if key in users:.
מה זה EOFError ב-Python?
EOFError (end-of-file, סוף הקובץ) נזרקת כש-input() לא מצליחה לקרוא כלום כי זרם הקלט נסגר. תראו אותה בעיקר כשמעבירים בצינור (pipe) לסקריפט שמשתמש ב-input() בלי נתונים, או כשלוחצים Ctrl-D בשורת הפקודה האינטראקטיבית. הגנו עם try/except EOFError אם הסקריפט חייב לטפל בקלט מצינור.
מה זה ModuleNotFoundError?
ModuleNotFoundError אומרת ש-import X לא מצאה מודול בשם X. או שהחבילה לא מותקנת (pip install X), או שהיא מותקנת ב-Python אחרת מזו שמריצה את הקוד שלכם (נפוץ מאוד כשיש כמה התקנות של Python). python -m pip install X פותרת את המקרה השני כי היא משתמשת באותו מפרש.
איך קוראים traceback ב-Python?
קוראים מלמטה למעלה. השורה האחרונה היא סוג החריגה וההודעה: הדבר הספציפי שהשתבש. השורות שמעליה מציגות את מחסנית הקריאות שהובילה לשם, כשהקוד שלכם נמצא בדרך כלל קרוב יותר לתחתית. לחצו או קפצו לקובץ ולשורה במסגרת התחתונה ביותר ששייכת לכם; שם נמצא התיקון.