Exceptions א': למה צריך טיפול בשגיאות, try ו-except
- להסביר מהו Exception ומה קורה לתוכנית כשהוא לא מטופל
- לכתוב בלוק
try/exceptשתופס שגיאה ומטפל בה - לטפל בכמה סוגי Exceptions בנפרד, ולקבל את פרטי השגיאה עם
as - להבין למה
except“ריק” שתופס הכול הוא הרגל מסוכן
מה נלמד?
- מהו Exception (חריגה).
- למה תוכנה אמיתית צריכה לטפל בשגיאות ולא לקרוס.
- התחביר
try/except. - טיפול שונה בסוגי שגיאות שונים.
למה צריך את זה?
דמיינו שרת מחשבון קטן: לקוח אחד שולח "hello" במקום מספר, והשרת כולו קורס. כל הלקוחות שבאים אחריו מקבלים “Connection refused”.
בעולם האמיתי:
- משתמש מקליד “abc” בשדה גיל. האפליקציה לא אמורה להיסגר.
- קובץ שהתוכנית מנסה לקרוא נמחק. צריך להודיע, לא לקרוס.
- האינטרנט נפל באמצע בקשה. כדאי לנסות שוב, או להציג הודעה.
שגיאות יקרו: קלט רע, קבצים חסרים, רשת לא יציבה. השאלה היא רק מה התוכנה עושה כשהן קורות.
הסבר מהבסיס
מהו Exception?
Exception הוא אובייקט ש-Python “זורקת” (Raise) כש-Runtime Error קורה. כל השגיאות שראינו אתמול (ValueError, ZeroDivisionError, TypeError…) הן Exceptions.
כש-Exception נזרק:
- Python עוצרת את השורה הנוכחית מיד.
- היא מחפשת מי “יתפוס” (Catch) את השגיאה.
- אם אף אחד לא תופס, התוכנית קורסת ומודפס Traceback.
ה-try / except הוא הדרך לתפוס.
לוליין שהולך על חבל (הקוד שלכם) עלול ליפול (Exception). בלי רשת, הנפילה היא סוף ההופעה (קריסה). try = “ההופעה מעל רשת”. except = הרשת, שתופסת את הנפילה, ואז מחליטים מה לעשות: לנסות שוב, להודיע לקהל, או להמשיך לקטע הבא.
איך זה עובד
try:— “נסה לבצע את הבלוק הזה”.except SomeError:— “אם בתוך ה-tryנזרקSomeError, קפוץ לכאן ובצע את הבלוק הזה במקום לקרוס”.- אם לא נזרקה שגיאה, בלוק ה-
exceptמדולג. - אחרי ה-
try/except, התוכנית ממשיכה כרגיל.
תחביר
try:
# code that might fail
except ErrorType:
# what to do if ErrorType happened
# several error types, each handled differently
try:
...
except ValueError:
...
except ZeroDivisionError:
...
# get the error object itself (its message)
except ValueError as error:
print(error)
# same handling for several types
except (ValueError, TypeError):
...דוגמת קוד ראשונה
week-2/safe_age.py:
text = input("Enter your age: ")
try:
age = int(text)
print("Next year you will be", age + 1)
except ValueError:
print("That is not a whole number:", text)
print("Program continues normally.")הרצה 1 (קלט תקין):
Enter your age: 30
Next year you will be 31
Program continues normally.הרצה 2 (קלט שגוי):
Enter your age: abc
That is not a whole number: abc
Program continues normally.פירוק הקוד שורה אחר שורה
| שורה | הרצה 1 (30) | הרצה 2 (abc) |
|---|---|---|
text = input(...) | text = "30" | text = "abc" |
try: | נכנסים לבלוק | נכנסים לבלוק |
age = int(text) | מצליח: age = 30 | נזרק ValueError, ו-Python קופצת מיד ל-except |
print("Next year...") | מודפס | מדולג. לא מגיעים אליה |
except ValueError: | מדולג (אין שגיאה) | נתפס! |
print("That is not...") | לא רץ | מודפס |
print("Program continues...") | מודפס | מודפס, והתוכנית לא קרסה |
הנקודה החשובה: ברגע שנזרקה שגיאה, שאר השורות ב-try לא רצות. לכן שמים בתוך ה-try רק את מה שתלוי בהצלחה.
דוגמה נוספת
מחשבון חלוקה עמיד, עם טיפול שונה לכל סוג שגיאה ולולאה שמבקשת שוב. week-2/safe_divide.py:
def ask_number(prompt):
while True:
text = input(prompt)
try:
return float(text)
except ValueError:
print("Please type a number, for example 12 or 3.5")
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
print("Cannot divide by zero - returning 0")
return 0
x = ask_number("First number: ")
y = ask_number("Second number: ")
print("Result:", divide(x, y))הרצה לדוגמה:
First number: ten
Please type a number, for example 12 or 3.5
First number: 10
Second number: 0
Cannot divide by zero - returning 0
Result: 0פירוק:
return float(text)בתוךtry— אם ההמרה מצליחה, הפונקציה מחזירה מיד ויוצאת גם מהלולאה. אם נכשלת,exceptמדפיס הודעה, והלולאה מבקשת שוב. כך “מחייבים” את המשתמש לקלט תקין.- כל פונקציה תופסת את השגיאה שרלוונטית אליה:
ask_numberאתValueError(קלט),divideאתZeroDivisionError(חישוב). - שאלה פתוחה: האם
divideצריכה להחזיר 0 בחלוקה באפס? מתמטית, זו תשובה שגויה! במקרים רבים עדיף לא לתפוס כאן, ולתת למי שקרא להחליט. נדבר על זה בשיעור עלraise.
as error — לקבל את פרטי השגיאה
try:
number = int("12.5")
except ValueError as error:
print("Conversion failed:", error)Conversion failed: invalid literal for int() with base 10: '12.5'error הוא אובייקט השגיאה. הדפסה שלו מציגה את ההודעה שהייתה מופיעה בשורה האחרונה של ה-Traceback.
טעות נפוצה
הטעות 1: except ריק שתופס הכול.
try:
age = int(input("Age: "))
print("Double:", age * 2)
reslt = age / 0
except:
print("Something went wrong")יש כאן שלוש בעיות שונות (קלט, חלוקה באפס, ואפילו אות שחסרה בשם המשתנה), וכולן מוסתרות תחת “Something went wrong”. אי אפשר לדבג. והגרוע מכל: except: ריק תופס גם KeyboardInterrupt, ולכן אפילו Ctrl + C לא יעצור תוכנית שתקועה בלולאה כזו!
הכלל: תופסים רק שגיאות ספציפיות שיודעים לטפל בהן. כל השאר ממשיכות למעלה וקורסות, וזה טוב, כי כך מגלים באגים.
הטעות 2: try ענק.
try:
# 40 lines of code...
except ValueError:
print("Invalid value")איזו מ-40 השורות נכשלה? לא ידוע. שמים ב-try רק את השורות שעלולות להיכשל בשגיאה שתופסים, כמה שפחות.
הטעות 3: “לבלוע” שגיאה בשקט.
try:
price = float(text)
except ValueError:
passpass אומר “אל תעשה כלום”. השגיאה נעלמת, ו-price אולי לא קיים בכלל, מה שיגרום ל-NameError מבלבל כמה שורות אחר כך. תמיד עושים משהו: הודעה, ערך ברירת מחדל מוסבר, או רישום ללוג.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: המרה בטוחה למספר
כתבו פונקציה to_number(text) שמחזירה את הטקסט כמספר שלם, ואם הטקסט אינו מספר — מחזירה None, בלי שהתוכנית תקרוס.
print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))פלט צפוי:
42
None
7הצג פתרון
def to_number(text):
try:
return int(text)
except ValueError:
return None
print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))42
None
7int("hello") זורקת ValueError. בלי try, התוכנית הייתה קורסת. עם except ValueError אנחנו מחליטים מה קורה במקום.
תרגיל 2: טיפול שונה לכל סוג שגיאה
כתבו פונקציה safe_divide(a_text, b_text) שמקבלת שני טקסטים, ממירה אותם למספרים ומחלקת. היא מחזירה:
- את תוצאת החלוקה, אם הכול תקין;
"numbers only"אם אחד הטקסטים אינו מספר;"cannot divide by zero"אם המחלק הוא 0.
print(safe_divide("10", "4"))
print(safe_divide("10", "0"))
print(safe_divide("ten", "2"))פלט צפוי:
2.5
cannot divide by zero
numbers onlyהצג פתרון
def safe_divide(a_text, b_text):
try:
return int(a_text) / int(b_text)
except ValueError:
return "numbers only"
except ZeroDivisionError:
return "cannot divide by zero"
print(safe_divide("10", "4"))
print(safe_divide("10", "0"))
print(safe_divide("ten", "2"))2.5
cannot divide by zero
numbers onlyלכל סוג שגיאה יש except משלו, עם טיפול מתאים. Python בודקת את בלוקי ה-except לפי הסדר ונכנסת לראשון שמתאים.
תרגיל 3: להציג את הודעת השגיאה
הקוד הבא נכשל עם ValueError. עטפו אותו ב-try, תפסו את השגיאה עם except ValueError as error, והדפיסו את הטקסט Could not convert: ואחריו את ההודעה ש-Python שמרה ב-error:
number = int("12abc")פלט צפוי:
Could not convert: invalid literal for int() with base 10: '12abc'הצג פתרון
try:
number = int("12abc")
except ValueError as error:
print("Could not convert:", error)Could not convert: invalid literal for int() with base 10: '12abc'as error שומר את אובייקט השגיאה במשתנה. כשמדפיסים אותו, מקבלים את ההודעה ש-Python יצרה.
תרגיל 4: בחירה מרשימה
נתונה רשימת שמות. כתבו פונקציה pick(names, position_text) שמקבלת מיקום כטקסט (מתחיל מ-1) ומחזירה את השם במיקום הזה. היא מחזירה "not a number" לקלט שאינו מספר, ו-"no such position" למיקום שלא קיים.
names = ["Dana", "Avi", "Noa"]
print(pick(names, "2"))
print(pick(names, "abc"))
print(pick(names, "9"))פלט צפוי:
Avi
not a number
no such positionהצג פתרון
def pick(names, position_text):
try:
position = int(position_text)
return names[position - 1]
except ValueError:
return "not a number"
except IndexError:
return "no such position"
names = ["Dana", "Avi", "Noa"]
print(pick(names, "2"))
print(pick(names, "abc"))
print(pick(names, "9"))Avi
not a number
no such positionשתי שגיאות שונות בשני מקומות: int(...) עלולה לזרוק ValueError, וגישה לרשימה עלולה לזרוק IndexError.
תרגיל 5: except שתופס הכול
def read_age(text):
try:
return int(text)
except:
return 0- למה
except:בלי סוג שגיאה מסוכן? - תנו דוגמה לבאג אמיתי שהקוד הזה היה מסתיר.
- תקנו את הקוד.
הצג פתרון
except:ריק תופס כל שגיאה — גם כאלה שלא חשבנו עליהן — ומסתיר אותן. הקוד ממשיך כאילו הכול בסדר, עם ערך שגוי.- אם מישהו יעביר בטעות
Noneבמקום טקסט, תיזרקTypeError— באג אמיתי בקוד שקרא לפונקציה. במקום שנראה אותו, נקבל בשקט גיל 0. (אפילוCtrl + Cנתפס על ידיexcept:ריק.) - תופסים רק את השגיאה שאנחנו מצפים לה:
def read_age(text):
try:
return int(text)
except ValueError:
return 0
print(read_age("30"))
print(read_age("thirty"))30
0סיכום
- Exception = שגיאה ש-Python זורקת בזמן ריצה. אם אף אחד לא תופס אותה, התוכנית קורסת.
try= “נסה”.except SomeError= “אם קרתה השגיאה הזו, עשה את זה במקום לקרוס”.- ברגע שנזרקת שגיאה, שאר ה-
tryמדולג. except SomeError as errorנותן גישה להודעת השגיאה.- תופסים שגיאות ספציפיות בלבד, עם
tryקטן. בליexcept:ריק ובליpassשבולע שגיאות.
דוקומנטציה רשמית
- Python Tutorial — Handling Exceptions — try ו-except
- Built-in Exceptions — ValueError, KeyError ועוד