Exceptions ב': else ו-finally — המבנה המלא
- להסביר מתי רץ כל אחד מארבעת הבלוקים — try, except, else, finally
- להשתמש ב-
elseלקוד שרץ רק אם לא הייתה שגיאה - להשתמש ב-
finallyלפעולות ניקוי שחייבות לקרות תמיד - לחזות את סדר ההדפסות בתוכנית עם try מלא
מה נלמד?
בשיעור הקודם השתמשנו ב-try וב-except. למבנה יש עוד שני חלקים:
else— רץ רק אם לא הייתה שגיאה.finally— רץ תמיד, עם שגיאה או בלעדיה.
למה צריך את זה?
דמיינו קוד שפותח חיבור ל-Database (נושא Backend ומסדי נתונים), מבצע פעולה וסוגר את החיבור. מה קורה אם הפעולה נכשלת באמצע? אם הסגירה כתובה אחרי הפעולה, היא לא תתבצע, והחיבור יישאר פתוח. אחרי מאה כישלונות כאלה, השרת יקרוס כי נגמרו לו החיבורים.
finally מבטיח שהניקוי יקרה בכל מקרה. else שומר על ה-try קטן, ומבהיר מה רץ רק אחרי הצלחה.
הסבר מהבסיס
ארבעת הבלוקים
| בלוק | מתי רץ | למה משמש |
|---|---|---|
try | תמיד מתחילים בו | הקוד שעלול להיכשל |
except | רק אם נזרקה שגיאה מהסוג המתאים | טיפול בשגיאה |
else | רק אם לא נזרקה שום שגיאה ב-try | המשך שתלוי בהצלחה |
finally | תמיד, בסוף, בכל מקרה | ניקוי: סגירת קבצים, חיבורים, הודעת סיום |
try = מנסים לאפות עוגה. except = אם נשרפה, מזמינים עוגה מהמאפייה. else = אם יצאה טובה, מקשטים אותה (לא מקשטים עוגה שרופה!). finally = בכל מקרה, מכבים את התנור ושוטפים כלים.
למה else ולא פשוט להמשיך ב-try?
אפשר לשים את קוד ההמשך בתוך ה-try. אבל אז, אם הקוד הזה נכשל באותה שגיאה, ה-except יתפוס אותו בטעות, ויטפל בבעיה הלא נכונה. else מבהיר: רק השורה המסוכנת בתוך try, והשאר בחוץ.
finally רץ גם כש…
- הייתה שגיאה ונתפסה.
- הייתה שגיאה ולא נתפסה.
finallyרץ, ואז התוכנית קורסת. - יש
returnבאמצע ה-try.finallyרץ לפני שהפונקציה באמת חוזרת.
תחביר
try:
# risky code
except SomeError:
# runs only if SomeError happened
else:
# runs only if NO error happened in try
finally:
# runs ALWAYS, lastסדר חובה: try ← except (אחד או יותר) ← else (אופציונלי) ← finally (אופציונלי). אפשר גם try + finally בלי except.
דוגמת קוד ראשונה
week-2/full_try.py:
def convert(text):
print("--- converting:", text)
try:
number = int(text)
except ValueError:
print("except: not a number")
else:
print("else: success, doubled =", number * 2)
finally:
print("finally: done with", text)
convert("21")
convert("hi")פלט:
--- converting: 21
else: success, doubled = 42
finally: done with 21
--- converting: hi
except: not a number
finally: done with hiפירוק הקוד שורה אחר שורה
| בלוק | convert("21") | convert("hi") |
|---|---|---|
try: number = int(text) | מצליח: number = 21 | ValueError |
except ValueError: | מדולג | רץ |
else: | רץ: מדפיס 42 | מדולג |
finally: | רץ | רץ |
else ו-except לעולם לא רצים שניהם: או שהייתה שגיאה, או שלא. ואילו finally רץ בשני המקרים.
דוגמה נוספת
“חיבור” שחייב להיסגר. כאן מדמים חיבור למשאב חיצוני (כמו Database), ורואים ש-finally סוגר גם כשיש כישלון באמצע, וגם כשיש return. week-2/connection_demo.py:
def open_connection():
print("[connection opened]")
def close_connection():
print("[connection closed]")
def fetch_price(product_name, prices):
open_connection()
try:
price = prices[product_name]
except KeyError:
print("No such product:", product_name)
return None
else:
print("Found", product_name)
return price
finally:
close_connection()
catalog = {"bread": 8, "milk": 6}
print("Result:", fetch_price("milk", catalog))
print()
print("Result:", fetch_price("cake", catalog))פלט:
[connection opened]
Found milk
[connection closed]
Result: 6
[connection opened]
No such product: cake
[connection closed]
Result: Noneפירוק:
catalog = {"bread": 8, "milk": 6}— זה dict (מילון): כל מפתח ("bread") ממופה לערך (8).prices["milk"]מחזיר 6. נלמד לעומק בנושא מבני נתונים ופונקציות. כרגע מספיק לדעת שגישה למפתח שלא קיים זורקתKeyError.- שימו לב: יש
returnגם ב-exceptוגם ב-else. ובכל זאת[connection closed]מודפס לפני ה-Result. כלומר,finallyרץ אחרי ה-return“הוחלט”, אבל לפני שהפונקציה באמת חוזרת. - בלי
finally, היינו צריכים לכתובclose_connection()לפני כלreturn. קל לשכוח, והרבה כפילות.
טעות נפוצה
הטעות 1: לשים ניקוי אחרי ה-try/except במקום ב-finally.
open_connection()
try:
price = prices[name]
except KeyError:
print("missing")
return None
close_connection() # never reached when except returns!במקרה של KeyError, ה-return יוצא מהפונקציה, ו-close_connection() לא רץ. החיבור נשאר פתוח. finally פותר בדיוק את זה.
הטעות 2: קוד ההמשך בתוך ה-try, ו-except שתופס את השגיאה הלא נכונה.
try:
age = int(text)
category = ["child", "adult"][age // 18] # can also raise IndexError... or ValueError?
except ValueError:
print("Age must be a number")אם שורת ההמשך תיכשל, למשל ב-IndexError עבור גיל 40 (40 // 18 = 2, ואין אינדקס 2), ההודעה לא קשורה בכלל. שימו בתוך ה-try רק את int(text), ואת ההמשך ב-else.
הטעות 3: לחשוב ש-finally “תופס” את השגיאה. הוא לא. אם אין except מתאים, finally רץ, ואחר כך התוכנית קורסת עם Traceback.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: חזו את הפלט
כתבו מה יודפס עבור כל קריאה — לפני שמריצים:
def half(text):
try:
number = int(text)
except ValueError:
print("not a number:", text)
else:
print("half is", number / 2)
finally:
print("checked", text)
half("8")
half("x")הצג פתרון
def half(text):
try:
number = int(text)
except ValueError:
print("not a number:", text)
else:
print("half is", number / 2)
finally:
print("checked", text)
half("8")
half("x")half is 4.0
checked 8
not a number: x
checked x- עבור
"8"אין שגיאה, ולכן רץelse, ואחריוfinally. - עבור
"x"נזרקתValueError, ולכן רץexcept— ו-elseלא רץ.finallyרץ בשני המקרים.
תרגיל 2: finally רץ גם אחרי return
def check(number):
try:
if number > 0:
return "positive"
return "not positive"
finally:
print("done checking", number)
print(check(5))
print(check(-2))מה יודפס, ובאיזה סדר? הסבירו למה.
הצג פתרון
def check(number):
try:
if number > 0:
return "positive"
return "not positive"
finally:
print("done checking", number)
print(check(5))
print(check(-2))done checking 5
positive
done checking -2
not positivefinally רץ לפני שהפונקציה באמת חוזרת — גם כשיש return בתוך ה-try. לכן “done checking” מודפס לפני הערך שהפונקציה החזירה.
תרגיל 3: ארבעת הבלוקים
כתבו פונקציה parse_quantity(text) שמשתמשת בכל ארבעת הבלוקים:
try— ממיר את הטקסט למספר;except— מדפיסinvalid: <text>;else— מדפיסquantity: <number>;finally— מדפיס---.
parse_quantity("3")
parse_quantity("three")פלט צפוי:
quantity: 3
---
invalid: three
---הצג פתרון
def parse_quantity(text):
try:
quantity = int(text)
except ValueError:
print("invalid:", text)
else:
print("quantity:", quantity)
finally:
print("---")
parse_quantity("3")
parse_quantity("three")quantity: 3
---
invalid: three
---הקוד שרץ רק כשהכול הצליח נמצא ב-else ולא בתוך ה-try — כך ה-try נשאר קטן, ותופס רק את השורה שבאמת עלולה להיכשל.
תרגיל 4: לסגור את מה שפתחנו
הפונקציות open_register() ו-close_register() פותחות וסוגרות קופה. כתבו פונקציה sell(prices, position) שפותחת את הקופה, מחזירה את המחיר במיקום שנבחר (המיקום הוא אינדקס של הרשימה — מתחיל מ-0), ותמיד סוגרת את הקופה — גם כשהמיקום לא קיים. מיקום לא קיים מחזיר None.
def open_register():
print("register opened")
def close_register():
print("register closed")
print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))פלט צפוי:
register opened
register closed
20
register opened
register closed
Noneהצג פתרון
def open_register():
print("register opened")
def close_register():
print("register closed")
def sell(prices, position):
open_register()
try:
return prices[position]
except IndexError:
return None
finally:
close_register()
print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))register opened
register closed
20
register opened
register closed
Noneזה בדיוק התפקיד של finally: ניקוי — סגירת משאב שנפתח, לא משנה איך הפונקציה הסתיימה.
תרגיל 5: else או בתוך ה-try?
שני מפתחים כתבו אותו קוד בשתי דרכים:
# A # B
try: try:
number = int(text) number = int(text)
print(100 / number) except ValueError:
except ValueError: print("not a number")
print("not a number") else:
print(100 / number)מה ההבדל, ואיזו גרסה עדיפה? מה יקרה בכל אחת כש-text הוא "0"?
הצג פתרון
- ב-A, גם החלוקה נמצאת בתוך ה-
try. ב-B, בתוך ה-tryיש רק את ההמרה, והחלוקה עברה ל-else. - כש-
textהוא"0", בשתי הגרסאות תיזרקZeroDivisionError, שאףexceptלא תופס, והתוכנית תקרוס. אבל ב-B ברור לגמרי שה-except ValueErrorשייך רק להמרה. - B עדיפה:
tryקטן ככל האפשר. כךexceptלא תופס בטעות שגיאות שמגיעות משורות אחרות, וקל לראות מה כל חלק עושה.
סיכום
try— קוד מסוכן.except— רק אם הייתה שגיאה מתאימה.else— רק אם לא הייתה שגיאה.finally— תמיד.exceptו-elseלעולם לא רצים יחד.finallyרץ גם אחריreturn, וגם כשהשגיאה לא נתפסה (ואז התוכנית קורסת אחריו).elseשומר את ה-tryקטן וממוקד.finallyהוא המקום לניקוי: סגירת קבצים וחיבורים.
דוקומנטציה רשמית
- Python Tutorial — Defining Clean-up Actions — finally
- The try statement — המבנה המלא: try / except / else / finally