What would you like to learn?

Try PowerShell, networks, XSS, or Step 138

Browse the full curriculum →

Python

Step 46. Exception Handling and Debugging — Programs That Get Back Up When They Fall

Step 46Estimated practice · 3 hours

Level 1 — Programming and the Computer’s Inner Workings | Difficulty ★★☆☆☆ | Estimated time: 3 hours

Prerequisites: Steps 41–45 complete. You can use functions, file I/O, and data structures.

  • What you need: a computer with Python installed and a text editor. Nothing new to install.
  • Caution: today’s exercise is 100% safe. There are many experiments that deliberately cause errors, but they all happen only inside the small scripts we create.

The programs we’ve made so far share one thing: they only deal with well-behaved users. Asked for a number, they get a number; files get opened only when they exist. But real users write "twenty" when asked their age, misspell file names, and hit Enter on an empty field. If the program spews red text and dies every time, it’s not a tool — it’s a glass figurine. Today we learn breakfalls for programs — not programs that never fall, but programs that get back up when they fall.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Read an error message (traceback) from the bottom up to find the cause and the scene
  • Distinguish the representative exceptions by name (ZeroDivisionError, ValueError, FileNotFoundError)
  • Build per-exception-type responses with try/except so the program doesn’t die
  • Build a "if wrong, ask again" input loop with the while + try combination
  • Make a function reject bad values on its own with raise

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3 (running script files)
Today’s grammar try / except, else, finally, raise, except ... as e
Exceptions you’ll meet today ZeroDivisionError, ValueError, FileNotFoundError
Concepts needed exceptions, tracebacks, defensive programming

2-1. Exceptions — The "I Can’t Go Any Further" Signal

When Python meets a situation it can’t handle during execution, it throws an exception and stops. Dividing by zero, converting text to a number, and opening a nonexistent file are the classics.

An exception is not Python getting angry — it’s an accurate situation report. Where, what, and why it failed are all written down. That’s why experts read error messages not as "failure" but as "the most honest documentation."

2-2. Tracebacks — The Incident Report

When an exception occurs, you get output like this (verified on 2026-09-09, file path shortened):

Traceback (most recent call last):
  File "e1_zero.py", line 1, in <module>
    print(1 / 0)
          ~~^~~
ZeroDivisionError: division by zero

This is called a traceback. The reading trick is bottom to top.

  1. The bottom line: ZeroDivisionError: division by zero — what the accident was (division by zero)
  2. The line above: File "e1_zero.py", line 1 — where it happened (line 1)
  3. Caret marks like ~~^~~: from Python 3.11 on, the position of the expression at fault is underlined for you

No matter how long it gets, the principle is the same: read from the bottom. This one ordering greatly speeds up debugging.

2-3. try/except — Setting Up the Safety Net

The syntax for catching exceptions is try/except.

try:
    x = int(input("Number: "))
    print(10 / x)
except ZeroDivisionError:
    print("You can't divide by zero.")
  • try block: where you put work that might be dangerous
  • except block: the countermeasure to run instead if that accident happens

Even when an exception occurs, the program doesn’t die — it runs the except countermeasure and keeps going. It’s falling and rolling with a breakfall, then standing back up.

2-4. Defensive Programming — Don’t Assume Users Are Fairies

The philosophy of exception handling has a name: defensive programming. An attitude that takes "input can always be weird" as the default assumption.

Ask for a number but prepare for text to arrive; open a file but prepare for it to be missing. With this attitude, programs grow solid — and this attitude itself is also the root of security thinking.


3. Follow Along

3-1. Kill It on Purpose — Meet the Three Exception Brothers

Input (e1_zero.py)

print(1 / 0)

Run it and the traceback from earlier appears. Now change the file.

Input (e2_value.py)

print(int("abc"))
Traceback (most recent call last):
  File "e2_value.py", line 1, in <module>
    print(int("abc"))
          ^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'abc'

(Verified on 2026-09-09, path shortened.)

Next.

Input (e3_file.py)

open("missing_file.txt")
Traceback (most recent call last):
  File "e3_file.py", line 1, in <module>
    open("missing_file.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'missing_file.txt'

(Verified on 2026-09-09, path shortened.)

How to read it: write the three exception names in your notebook. The name is the diagnosis itself — ZeroDivisionError (divided by zero), ValueError (unsuitable value), FileNotFoundError (file not found). They’re friends you’ll meet hundreds more times. And notice: once an exception occurs, the code below it never runs. The program stops right there.

Predict: what exception will int("3.14") raise? "3.14" is a decimal, but int wants an integer. Predict, then check. (Verified result on 2026-09-09: ValueError: invalid literal for int() with base 10: '3.14' — even a decimal-point string gets refused integer conversion.)

3-2. First Safety Net — Division That Won’t Die

Input (safe1.py)

try:
    x = int(input("Enter a number: "))
    print(f"10 divided by {x} is {10 / x}")
except ValueError:
    print("That's not a number! Please enter digits.")
except ZeroDivisionError:
    print("You can't divide by zero.")
print("The program ended normally.")

Test with three kinds of input. The case of entering 0 (verified on 2026-09-09):

Enter a number: You can't divide by zero.
The program ended normally.

The case of entering text:

Enter a number: That's not a number! Please enter digits.
The program ended normally.

The case of valid input (5):

Enter a number: 10 divided by 5 is 2.0
The program ended normally.

(Verified on 2026-09-09. The entered value prints right after the prompt.)

How to read it: attach two excepts and each accident type gets a different response. ValueError (text input) and ZeroDivisionError (zero input) each have their own line. And in every case, the last line runs — proof the program didn’t die.

Why: different accidents need different responses so the user knows the next action. "Please retype" and "zero isn’t allowed" are different guidance.

3-3. Close Reading of a Traceback

Let’s deliberately make a slightly longer incident.

Input (trace.py)

def divide(a, b):
    return a / b

def start():
    x = 10
    y = 0
    print(divide(x, y))

start()

Output (verified on 2026-09-09, path and caret marks shortened):

Traceback (most recent call last):
  File "trace.py", line 10, in <module>
    start()
  File "trace.py", line 8, in start
    print(divide(x, y))
  File "trace.py", line 3, in divide
    return a / b
ZeroDivisionError: division by zero

How to read it: read bottom to top. The diagnosis is division by zero; the accident scene is line 3 (inside divide); the path that led there is stacked as line 8 → line 10. The deeper functions go, the longer the trail of footprints, but the reading method is identical. Diagnosis from the last line; the real scene from the bottom-most File line.

Why: now that you’ve learned functions, most errors will come as "multi-layered trails" like this. Today you learn how not to be intimidated.

3-4. Asking Again — The Appeal Loop

Combine except with while and you get a patient program that "asks again when wrong."

Input (patient.py)

while True:
    try:
        age = int(input("Enter your age as a number: "))
        break
    except ValueError:
        print("That's not a number. Please try again.")
print(f"You're {age}. Thank you!")

A run where "abc", "fifteen", and an empty field (just Enter) were entered in turn, then finally 15 (verified 2026-09-09):

Enter your age as a number: That's not a number. Please try again.
Enter your age as a number: That's not a number. Please try again.
Enter your age as a number: That's not a number. Please try again.
Enter your age as a number: You're 15. Thank you!

How to read it: every wrong input makes except say its line and while loops back to the start. An empty field also gets the same line because int("") raises ValueError. When a valid input arrives, try succeeds and break escapes. "Ask again until it succeeds" is the national-standard pattern of user input handling.

3-5. The File Safety Net — Giving the Notepad a Breakfall

Let’s give Step 45’s notepad a breakfall.

Input (safememo.py)

try:
    with open("diary.txt", "r", encoding="utf-8") as f:
        print(f.read())
except FileNotFoundError:
    print("No diary yet. Start with your first memo!")

Run with no diary.txt present (verified on 2026-09-09):

No diary yet. Start with your first memo!

How to read it: in Step 45 we asked in advance with os.path.exists. Today’s style is "just try it, and handle the accident if it happens." Both styles are widely used, and the Python world tends to prefer the try style. Know both.

3-6. finally and else — Work to Do Regardless of Accidents

Input (elsefinally.py)

try:
    x = int(input("Number: "))
except ValueError:
    print("That's not a number.")
else:
    print("Input succeeded! Starting calculation.")
finally:
    print("--- Input processing done ---")

The case of entering 7 and the case of entering abc (verified on 2026-09-09):

Input succeeded! Starting calculation.
--- Input processing done ---
That's not a number.
--- Input processing done ---

How to read it: else runs "only when there was no accident"; finally runs "unconditionally, accident or not." That both runs show --- Input processing done --- at the end is the proof of finally. finally’s representative use is finishing work that must happen in any case — "closing files, cleanup."

3-7. raise — We Can Throw Exceptions Too

Python isn’t the only one that can throw exceptions. Our code can throw one too when it judges "this isn’t right."

Input (raisetest.py)

def set_age(age):
    if age < 0 or age > 150:
        raise ValueError("Age must be between 0 and 150.")
    return age

try:
    set_age(-5)
except ValueError as e:
    print("Rejected:", e)

Output (verified on 2026-09-09):

Rejected: Age must be between 0 and 150.

How to read it: raise ExceptionName("message") is "accident declared here!" The e in except ... as e holds that message. This pattern — "a function rejects values it must not accept" — is a device by which the function’s maker protects its users.

Why: later, when you build tools, loudly rejecting misuse is far safer than quietly letting it pass. A quiet misdiagnosis is the most dangerous thing.


4. Missions & Exercises

Mission — The Invincible Calculator

Build the invincible calculator based on Step 44’s menu-driven calculator. Requirements:

  1. Everywhere a number is asked, it doesn’t die on text, empty fields, or weird symbols — it asks again
  2. Entering 0 in division doesn’t kill it — it prints "You can’t divide by zero"
  3. An out-of-range number or text for the menu choice gets guidance: "Enter 1-4 or 0"
  4. It absolutely never dies until the user picks 0 (exit)

How to test: feed it ten or more bad inputs yourself. If possible, ask family or a friend to "try to break it." Defense is completed when tested aggressively.

Exercises

Question 1. A long traceback was printed. Which line should you read first, and what does each line tell you?

Question 2. What exception does running int("3.14") raise, and why?

Question 3. If you write except: with no exception name, what problem arises?

Question 4. Explain why putting dozens of lines of code in a try block is a bad habit.


Answers & completion criteria · expand/collapse

5. Model Answers & Completion Criteria

Mission Model Answer

An example of the core structure (the full code is Step 44’s calculator with the pattern below applied):

def ask_number(prompt):
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            print("Please enter a number.")

while True:
    print("1.Add 2.Subtract 3.Multiply 4.Divide 0.Exit")
    menu = input("Choice: ").strip()
    if menu == "0":
        break
    if menu not in ["1", "2", "3", "4"]:
        print("Enter 1-4 or 0.")
        continue
    a = ask_number("First number: ")
    b = ask_number("Second number: ")
    if menu == "1":
        print("Result:", a + b)
    elif menu == "2":
        print("Result:", a - b)
    elif menu == "3":
        print("Result:", a * b)
    else:
        try:
            print("Result:", a / b)
        except ZeroDivisionError:
            print("You can't divide by zero.")

How to verify: ① Does it ask again when "abc", an empty field, or "3.14" goes into a number slot? ② Does entering 0 in division show the guidance and return to the menu? ③ Does entering "9" or "menu" at the menu show guidance? ④ After passing through all of this, does it still not end before you press 0? Four ‘yes’es and you pass.

Design point: extracting "ask for a number" into a function (ask_number) lets you write the while + try loop once and reuse it. Examples of ten bad-input tests: text, empty field, decimal, negative, a huge number, spaces only, special characters, spelled-out numbers ("three"), a tab character, 0.5 at the menu.

Exercise Answers

Answer 1. Read from the bottom line up. The bottom line is the diagnosis (exception name and reason), the File line above it is the accident scene (file name and line number), and the File lines further up are the call path that led there. The order is "diagnosis → scene → path."

Answer 2. It raises ValueError (verified on 2026-09-09: ValueError: invalid literal for int() with base 10: '3.14'). Because "3.14" is a string representing a decimal, it doesn’t fit the integer conversion rule. What int() accepts is only integer-shaped strings like "10" or "-3".

Answer 3. A bare except means "swallow every accident," so it quietly hides not only the accidents you expected but also real bugs like typos. You can confirm this by experiment — deliberately mistyping pritn and wrapping it in a bare except made it pass silently with no error report (verified on 2026-09-09). A quiet bug is the hardest bug to find. The principle is to write a specific name, like except ValueError:.

Answer 4. With a wide try, you catch the exception but can’t pinpoint which line caused it. The iron rule is to set the safety net only over the one or two dangerous lines; if dangerous lines are in several places, split the try into several.

Completion Criteria Checklist

  • [ ] I can read a traceback from the bottom up and find the diagnosis and the scene
  • [ ] I can distinguish ZeroDivisionError, ValueError, and FileNotFoundError
  • [ ] I can build per-exception-type responses with try/except
  • [ ] I can build "ask again when wrong" with while + try
  • [ ] I can explain the execution conditions of else and finally
  • [ ] I can make a function reject bad values with raise
  • [ ] Mission: I completed the invincible calculator and tested it with ten bad inputs

6. Common Pitfalls & Fixes

Wall 1. It dies even though I added an except

Symptom: you used try/except but red text still appears.
Cause: two representative cases. (a) The line that raised the exception is outside the try block. (b) The exception type you catch is different — a ValueError-catching except, but a FileNotFoundError occurred.
Fix: check whether the exception name at the bottom of the traceback matches the name on your except, and whether the erroring line is inside the try. The safety net must be set only above the performance area.

Wall 2. The try is too wide

Symptom: it catches the exception, but you can’t tell where it came from.
Cause: you put dozens of lines of code in the try block. With a wide net, the accident scene can’t be pinpointed.
Fix: put only "the truly dangerous one or two lines" in a try. If dangerous lines are in several places, split the try into several.

Wall 3. Slapping on a bare except (except:)

Symptom: you wrote except: with no exception type, and even a typo bug got swallowed, so you searched for the cause for ages.
Cause: a bare except swallows every exception — even the NameError from a syntax slip. In the 2026-09-09 experiment, wrapping pritn("typo") in a bare except let it pass silently with no report.
Fix: write exception names concretely, like except ValueError:. The act of writing "what accident am I expecting" is design in itself.

Wall 4. Reading the error message from the top

Symptom: when a long traceback appears, you read from the first line and burn out.
Cause: the order is reversed. The first line merely "announces that an incident occurred"; the diagnosis is at the very bottom.
Fix: always start from the bottom line. Get the order into your hands: "diagnosis (bottom) → scene (the File line above) → path (further up)."

Wall 5. Empty input behaves unexpectedly

Symptom: you just hit Enter and instead of "not a number," it dies or a weird value goes in.
Cause: input() returns the empty string "" for empty input, and int("") raises ValueError. If an except exists, asking again is normal behavior; if it died, you converted outside the try.
Fix: use 3-4’s loop structure as-is. An empty field gets filtered naturally as one kind of bad input (confirmed by testing on 2026-09-09).


7. Summary

Today’s Concepts

Concept One-line description
Exception Python’s accurate situation report — when raised, it stops right there
Traceback The incident report. From the bottom: diagnosis → scene → path
Defensive programming Taking "input can always be weird" as the default assumption
ZeroDivisionError Division by zero
ValueError A value doesn’t fit the rule (text→number conversion failure, etc.)
FileNotFoundError Tried to open a nonexistent file

Today’s Grammar

Grammar What it does
try: Starts the block holding dangerous work
except ExceptionName: The countermeasure to run if that accident happens
except ... as e: Receive the exception message as e
else: Runs only when there was no accident
finally: Runs unconditionally, accident or not
raise ExceptionName("message") Our code declares an accident
while True + try + break The "ask again when wrong" national pattern

Instincts More Important Than Commands

When an error pops up, a beginner closes the window, an intermediate reads the message, and an expert reproduces it. "On what input did it die? Let’s feed the same input again." Reproduce → diagnose (bottom line) → hypothesize → confirm with print → fix. Make these five beats a habit starting today. And once fixed, always retest with the same input — a retest is the only thing that proves a "fix."

Let me add one security perspective. A traceback is friendly documentation for the developer, but shown raw to users, it amounts to disclosing internal structure like file paths and function names. That’s why real services do dual processing: "record in detail internally, but show the user only ‘a temporary error occurred.’" For now your work is lab practice, so it doesn’t matter — but remember this principle.

Today your program learned breakfalls, and you yourself learned to read red text not as fear but as documentation. Next time you meet red text, read from the bottom, calmly, as if listening to an assistant’s report. The answer will always be visible.


Once every box is checked, Step 46 is complete.

ONE STEP FURTHER

Finished this lesson?

Check the completion criteria, then mark your progress.

Something wrong with this page or a link? Let us know.
Next