# Exceptions א': למה צריך טיפול בשגיאות, try ו-except<no value>

## מה נלמד?

- מהו **Exception** (חריגה).
- למה תוכנה אמיתית צריכה **לטפל** בשגיאות ולא לקרוס.
- התחביר `try` / `except`.
- טיפול **שונה** בסוגי שגיאות שונים.

## למה צריך את זה?

דמיינו שרת מחשבון קטן: לקוח אחד שולח `"hello"` במקום מספר, **והשרת כולו קורס**. כל הלקוחות שבאים אחריו מקבלים "Connection refused".

בעולם האמיתי:

- משתמש מקליד "abc" בשדה גיל. האפליקציה לא אמורה להיסגר.
- קובץ שהתוכנית מנסה לקרוא נמחק. צריך להודיע, לא לקרוס.
- האינטרנט נפל באמצע בקשה. כדאי לנסות שוב, או להציג הודעה.

שגיאות **יקרו**: קלט רע, קבצים חסרים, רשת לא יציבה. השאלה היא רק מה התוכנה **עושה** כשהן קורות.

## הסבר מהבסיס

### מהו Exception?

**Exception** הוא אובייקט ש-Python **"זורקת"** (Raise) כש-Runtime Error קורה. כל השגיאות שראינו אתמול (`ValueError`, `ZeroDivisionError`, `TypeError`...) הן Exceptions.

כש-Exception נזרק:

1. Python **עוצרת** את השורה הנוכחית מיד.
2. היא מחפשת מי **"יתפוס"** (Catch) את השגיאה.
3. אם אף אחד לא תופס, **התוכנית קורסת** ומודפס Traceback.

ה-`try` / `except` הוא הדרך **לתפוס**.

{{< box "analogy" "אנלוגיה: רשת ביטחון בקרקס" >}}
לוליין שהולך על חבל (הקוד שלכם) עלול ליפול (Exception). בלי רשת, הנפילה היא סוף ההופעה (קריסה). **`try`** = "ההופעה מעל רשת". **`except`** = הרשת, שתופסת את הנפילה, ואז מחליטים מה לעשות: לנסות שוב, להודיע לקהל, או להמשיך לקטע הבא.
{{< /box >}}

### איך זה עובד

- **`try:`** — "נסה לבצע את הבלוק הזה".
- **`except SomeError:`** — "אם בתוך ה-`try` נזרק `SomeError`, **קפוץ לכאן** ובצע את הבלוק הזה במקום לקרוס".
- אם לא נזרקה שגיאה, בלוק ה-`except` **מדולג**.
- אחרי ה-`try`/`except`, התוכנית **ממשיכה כרגיל**.

## תחביר

```python
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`:

```python
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 (קלט תקין):**

```text
Enter your age: 30
Next year you will be 31
Program continues normally.
```

**הרצה 2 (קלט שגוי):**

```text
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`:

```python
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))
```

**הרצה לדוגמה:**

```text
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` — לקבל את פרטי השגיאה

```python
try:
    number = int("12.5")
except ValueError as error:
    print("Conversion failed:", error)
```

```text
Conversion failed: invalid literal for int() with base 10: '12.5'
```

`error` הוא אובייקט השגיאה. הדפסה שלו מציגה את **ההודעה** שהייתה מופיעה בשורה האחרונה של ה-Traceback.

## טעות נפוצה

**הטעות 1: `except` ריק שתופס הכול.**

```python
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` ענק.**

```python
try:
    # 40 lines of code...
except ValueError:
    print("Invalid value")
```

איזו מ-40 השורות נכשלה? לא ידוע. **שמים ב-`try` רק את השורות שעלולות להיכשל בשגיאה שתופסים**, כמה שפחות.

**הטעות 3: "לבלוע" שגיאה בשקט.**

```python
try:
    price = float(text)
except ValueError:
    pass
```

`pass` אומר "אל תעשה כלום". השגיאה נעלמת, ו-`price` אולי לא קיים בכלל, מה שיגרום ל-`NameError` מבלבל כמה שורות אחר כך. תמיד **עושים משהו**: הודעה, ערך ברירת מחדל מוסבר, או רישום ללוג.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## תרגול

התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.

### תרגיל 1: המרה בטוחה למספר

כתבו פונקציה `to_number(text)` שמחזירה את הטקסט כמספר שלם, ואם הטקסט אינו מספר — מחזירה `None`, בלי שהתוכנית תקרוס.

```python
print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))
```

**פלט צפוי:**

```text
42
None
7
```

{{< solution >}}
```python
def to_number(text):
    try:
        return int(text)
    except ValueError:
        return None


print(to_number("42"))
print(to_number("hello"))
print(to_number("7"))
```

```text
42
None
7
```

`int("hello")` זורקת `ValueError`. בלי `try`, התוכנית הייתה קורסת. עם `except ValueError` אנחנו מחליטים מה קורה במקום.
{{< /solution >}}

### תרגיל 2: טיפול שונה לכל סוג שגיאה

כתבו פונקציה `safe_divide(a_text, b_text)` שמקבלת שני טקסטים, ממירה אותם למספרים ומחלקת. היא מחזירה:

- את תוצאת החלוקה, אם הכול תקין;
- `"numbers only"` אם אחד הטקסטים אינו מספר;
- `"cannot divide by zero"` אם המחלק הוא 0.

```python
print(safe_divide("10", "4"))
print(safe_divide("10", "0"))
print(safe_divide("ten", "2"))
```

**פלט צפוי:**

```text
2.5
cannot divide by zero
numbers only
```

{{< solution >}}
```python
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"))
```

```text
2.5
cannot divide by zero
numbers only
```

לכל סוג שגיאה יש `except` משלו, עם טיפול מתאים. Python בודקת את בלוקי ה-`except` לפי הסדר ונכנסת לראשון שמתאים.
{{< /solution >}}

### תרגיל 3: להציג את הודעת השגיאה

הקוד הבא נכשל עם `ValueError`. עטפו אותו ב-`try`, תפסו את השגיאה עם `except ValueError as error`, והדפיסו את הטקסט `Could not convert:` ואחריו את ההודעה ש-Python שמרה ב-`error`:

```python
number = int("12abc")
```

**פלט צפוי:**

```text
Could not convert: invalid literal for int() with base 10: '12abc'
```

{{< solution >}}
```python
try:
    number = int("12abc")
except ValueError as error:
    print("Could not convert:", error)
```

```text
Could not convert: invalid literal for int() with base 10: '12abc'
```

`as error` שומר את אובייקט השגיאה במשתנה. כשמדפיסים אותו, מקבלים את ההודעה ש-Python יצרה.
{{< /solution >}}

### תרגיל 4: בחירה מרשימה

נתונה רשימת שמות. כתבו פונקציה `pick(names, position_text)` שמקבלת מיקום כטקסט (מתחיל מ-1) ומחזירה את השם במיקום הזה. היא מחזירה `"not a number"` לקלט שאינו מספר, ו-`"no such position"` למיקום שלא קיים.

```python
names = ["Dana", "Avi", "Noa"]
print(pick(names, "2"))
print(pick(names, "abc"))
print(pick(names, "9"))
```

**פלט צפוי:**

```text
Avi
not a number
no such position
```

{{< solution >}}
```python
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"))
```

```text
Avi
not a number
no such position
```

שתי שגיאות שונות בשני מקומות: `int(...)` עלולה לזרוק `ValueError`, וגישה לרשימה עלולה לזרוק `IndexError`.
{{< /solution >}}

### תרגיל 5: except שתופס הכול

```python
def read_age(text):
    try:
        return int(text)
    except:
        return 0
```

1. למה `except:` בלי סוג שגיאה מסוכן?
2. תנו דוגמה לבאג אמיתי שהקוד הזה היה מסתיר.
3. תקנו את הקוד.

{{< solution >}}
1. `except:` ריק תופס **כל** שגיאה — גם כאלה שלא חשבנו עליהן — ומסתיר אותן. הקוד ממשיך כאילו הכול בסדר, עם ערך שגוי.
2. אם מישהו יעביר בטעות `None` במקום טקסט, תיזרק `TypeError` — באג אמיתי בקוד שקרא לפונקציה. במקום שנראה אותו, נקבל בשקט גיל 0. (אפילו `Ctrl + C` נתפס על ידי `except:` ריק.)
3. תופסים רק את השגיאה שאנחנו מצפים לה:

```python
def read_age(text):
    try:
        return int(text)
    except ValueError:
        return 0


print(read_age("30"))
print(read_age("thirty"))
```

```text
30
0
```
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **Exception** = שגיאה ש-Python זורקת בזמן ריצה. אם אף אחד לא תופס אותה, התוכנית קורסת.
- `try` = "נסה". `except SomeError` = "אם קרתה השגיאה הזו, עשה את זה במקום לקרוס".
- ברגע שנזרקת שגיאה, **שאר ה-`try` מדולג**.
- `except SomeError as error` נותן גישה להודעת השגיאה.
- תופסים **שגיאות ספציפיות** בלבד, עם `try` **קטן**. בלי `except:` ריק ובלי `pass` שבולע שגיאות.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## דוקומנטציה רשמית

- [Python Tutorial — Handling Exceptions](https://docs.python.org/3/tutorial/errors.html#handling-exceptions) — try ו-except
- [Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions) — ValueError, KeyError ועוד

<!-- docs:end -->
