פוסט
חריגות ב-Python: try, except, else, finally
מה קורה כש-Python נתקלת בשגיאה בזמן ריצה: קריאת traceback, תפיסת שגיאות עם try/except, else ו-finally, העלאת חריגות משלכם, ולמה except ריק הוא מלכודת.
IndexError, KeyError, ValueError ו-TypeError עצרו את התוכנית בפוסטים הקודמים, והפוסט על TypeError הראה try/except ראשון. המדריך הרשמי מנסח את זה כך: “Errors detected during execution are called exceptions and are not unconditionally fatal”.
כל הדוגמאות הורצו עם Python 3.13 ונבדקו מול המדריך הרשמי על שגיאות וחריגות והתיעוד של החריגות המובנות.
קריאת traceback
כשאף אחד לא מטפל בחריגה, Python מדפיסה traceback ועוצרת. קוראים אותו מלמטה למעלה: השורה האחרונה אומרת מה השתבש, והשורות שמעליה אומרות איפה:
def average(numbers):
return sum(numbers) / len(numbers)
print(average([]))
Traceback (most recent call last):
File "app.py", line 4, in <module>
print(average([]))
~~~~~~~^^^^
File "app.py", line 2, in average
return sum(numbers) / len(numbers)
~~~~~~~~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero
המסגרת שממש מעל השורה האחרונה היא השורה שנכשלה, בתוך average, ומעליה הקריאה שהובילה לשם. הסימנים ~ ו-^ מצביעים על החלק המדויק בשורה.
try ו-except
שמים קוד שעלול להיכשל בבלוק try. אם קורית חריגה מהסוג שכתוב ב-except, Python קופצת לבלוק ה-except במקום לעצור:
text = "12a"
try:
number = int(text)
print("parsed", number)
except ValueError:
print("not a whole number:", text)
print("still running")
not a whole number: 12a
still running
שאר בלוק ה-try מדולג ברגע שקורית חריגה, אז print("parsed", ...) לא רץ אף פעם.
except ValueError as e נותן שם לאובייקט החריגה. str(e) הוא ההודעה שלה:
try:
int("12a")
except ValueError as e:
print("error:", e)
print(type(e).__name__)
error: invalid literal for int() with base 10: '12a'
ValueError
תפיסה של כמה סוגים
ל-try יכולים להיות כמה בלוקים של except. Python בודקת אותם לפי הסדר ומריצה את הראשון שמתאים. except אחד יכול גם לקבל tuple של סוגים:
def safe_ratio(a, b):
try:
return a / b
except ZeroDivisionError:
return "can't divide by zero"
except (TypeError, ValueError) as e:
return f"bad input: {e}"
print(safe_ratio(6, 3))
print(safe_ratio(6, 0))
print(safe_ratio(6, "3"))
2.0
can't divide by zero
bad input: unsupported operand type(s) for /: 'int' and 'str'
סוג חריגה תופס גם את תתי-הסוגים שלו. LookupError תופס גם IndexError וגם KeyError, כי שניהם סוגים של שגיאות חיפוש. עץ המשפחה המלא נמצא בסוף התיעוד של החריגות המובנות.
else ו-finally
else רץ רק אם בלוק ה-try הסתיים בלי חריגה. finally רץ בכל מקרה: אחרי הצלחה, אחרי חריגה שטופלה, ואפילו כשחריגה עולה הלאה למי שקרא לפונקציה. זה המקום לניקוי:
def read_number(text):
try:
value = int(text)
except ValueError:
print("bad:", text)
else:
print("good:", value)
finally:
print("done with", repr(text))
read_number("7")
read_number("x")
good: 7
done with '7'
bad: x
done with 'x'
למה else ולא פשוט עוד שורות בתוך ה-try? קוד ב-else לא מוגן על ידי ה-except, אז שגיאה לא צפויה שם לא מוסתרת. שומרים את בלוק ה-try קטן, רק השורה שעלולה להיכשל.
בקבצים, with open(...) כבר עושה את הניקוי ש-finally היה עושה, וסוגר את הקובץ גם כשקורית חריגה.
העלאת חריגות משלכם
עם raise מעלים חריגה בעצמכם, למשל כשפונקציה מקבלת ערך שהיא לא יכולה לעבוד איתו. בוחרים את הסוג המובנה שמתאים, כדי שמי שקורא יוכל לתפוס בדיוק אותו, וכותבים הודעה שאומרת מה לא היה בסדר:
def set_age(age):
if age < 0:
raise ValueError(f"age can't be negative: {age}")
return age
try:
set_age(-3)
except ValueError as e:
print(e)
age can't be negative: -3
בתוך בלוק except, raise לבד מעלה מחדש את אותה חריגה, אחרי שרשמתם אותה ללוג או הוספתם פרטים. לסוגי שגיאה משלכם מגדירים מחלקה שיורשת מ-Exception. מחלקות הן נושא לפוסט אחר, אבל הצורה קצרה: class OutOfStock(Exception): pass.
לא תופסים הכול
except: ריק או except Exception: תופסים כל שגיאה, כולל שגיאות הקלדה שלכם:
prices = {"tea": 12}
try:
total = prices["tea"] * quantty
except Exception:
total = 0
print(total)
0
quantty הוא שם עם שגיאת כתיב, אז השגיאה האמיתית היא NameError. ה-except הסתיר אותה, והתוכנית המשיכה עם סכום שגוי. תופסים את החריגות הספציפיות שמצפים להן (כאן, KeyError למוצר שלא קיים), ונותנים לכל השאר להיכשל בגלוי.
טעויות נפוצות
- תפיסה במקום בדיקה פשוטה.
if key in dברור יותר מתפיסתKeyErrorבחיפוש פשוט. משתמשים ב-tryכשבדיקה מראש מסורבלת, כמו בפענוח מספר. - סדר שגוי של בלוקי
except. סוג רחב לפני סוג צר תופס הכול קודם. KeyErrorבא לפניLookupError.
מה הלאה
הצעד הבא בסדרה: pip, הכלי שמתקין חבילות שאנשים אחרים כתבו.