Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 6 of 9

An uncaught exception kills your program. try/except lets you catch it and carry on.

try:
    result = 10 / 0
    print("This never runs.")
except ZeroDivisionError:
    print("Cannot divide by zero.")

print("Program continues.")

Python runs the try block. If an exception is raised, it jumps straight to a matching except block; if not, except is skipped entirely.

Catching the right thing

Name the specific exception you expect:

def to_int(text):
    try:
        return int(text)
    except ValueError:
        return None

print(to_int("42"))
print(to_int("hello"))

Don't catch everything

This is the most common bad habit in error handling:

try:
    result = 10 / 0
except:
    print("Something went wrong")

A bare except: swallows everything — including typos in your own code, and KeyboardInterrupt when the user tries to quit. You'll be left staring at "Something went wrong" with no idea what broke.

Catch what you can actually handle, and let the rest crash loudly. A crash with a traceback is far more useful than a program that silently misbehaves.

Multiple except blocks

def safe_divide(a, b):
    try:
        return int(a) / int(b)
    except ValueError:
        return "Not a number"
    except ZeroDivisionError:
        return "Cannot divide by zero"

print(safe_divide("10", "2"))
print(safe_divide("abc", "2"))
print(safe_divide("10", "0"))

Python takes the first matching block, so order from specific to general — a broad except Exception first would shadow everything after it.

Handle several the same way by grouping them in a tuple:

try:
    value = int("abc")
except (ValueError, TypeError) as e:
    print(f"Bad input: {type(e).__name__}")

as e — inspecting the exception

try:
    with open("missing.txt") as f:
        content = f.read()
except FileNotFoundError as e:
    print(f"Could not open the file: {e}")

else

The else block runs only if no exception was raised:

def read_count(text):
    try:
        number = int(text)
    except ValueError:
        return "Not a number"
    else:
        return number * 2

print(read_count("5"))
print(read_count("abc"))

Why bother? It keeps the try block down to just the line that might fail. Anything in try that can't raise the error you're catching risks catching an unrelated bug by accident.

finally

The finally block runs no matter what — exception or not, even after a return:

def process():
    try:
        return 10 / 0
    except ZeroDivisionError:
        return "handled"
    finally:
        print("Cleanup always runs.")

print(process())

finally is for cleanup: closing connections, releasing locks. You'll notice this is exactly what with does for files — and with is the better tool when one exists.

A practical example

import json

def load_config(path):
    try:
        with open(path) as f:
            return json.load(f)
    except FileNotFoundError:
        return {"theme": "default"}
    except json.JSONDecodeError:
        return {"theme": "default", "error": "corrupt file"}

print(load_config("no-such-file.json"))

with open("bad.json", "w") as f:
    f.write("{not valid json")

print(load_config("bad.json"))

Two different failures, two sensible fallbacks, no crash.

Try it yourself

Handle a missing key, a bad conversion, and a division by zero.

Checkpoint

  • try/except catches an exception so the program keeps running.
  • Catch specific exceptions; never use a bare except:.
  • The first matching except wins — order specific before general.
  • as e gives you the exception object.
  • else runs when nothing failed; finally runs either way, for cleanup.

Questions about this lesson

Log in to ask a question.