Dataslope logoDataslope

Exceptions

try, except, else, finally, raising, and writing your own exception classes

When something goes wrong, Python raises an exception. If nothing catches it, the program prints a traceback and exits. Catching is done with try / except.

Exceptions are Python's way of encoding expected failure paths: file not found, network down, invalid input, permission denied. They let you separate "happy path" logic from error handling.

What happens when exceptions are not handled?

If an exception is raised and no try/except catches it, Python does three things:

  1. Prints a traceback showing the call stack (which function called which).
  2. Exits the program with a non-zero exit code (signals failure to the shell).
  3. (In a web context) Returns a 500 Internal Server Error to the client (or crashes the worker process, depending on the framework).
Code Block
Python 3.13.2

The traceback shows the exception propagation up the call stack: main() called divide(), which raised ZeroDivisionError. Since main() didn't catch it, it propagated up to the caller (in this case, our outer try block).

In production, unhandled exceptions are bugs. Your job is to anticipate failure modes (try opening a file; it might not exist) and handle them gracefully.

Real-world: exceptions encode expected failures

Exceptions are not like Java checked exceptions (which force you to declare every possible error). They are Python's way of saying "this can fail, and you should decide what to do."

Common patterns:

  • File I/O: FileNotFoundError, PermissionError, OSError
  • Network: requests.ConnectionError, requests.Timeout, socket.timeout
  • Parsing: json.JSONDecodeError, ValueError (from int("oops"))
  • APIs: Custom exceptions like stripe.error.RateLimitError

You choose which to catch. If you catch FileNotFoundError, you're saying "I expect this file might not exist, and I know what to do." If you don't catch it, you're saying "if the file doesn't exist, the program should crash."

This is the EAFP philosophy: Easier to Ask for Forgiveness than Permission.

# LBYL (Look Before You Leap) — Pythonic but verbose
if os.path.exists(path):
    with open(path) as f:
        data = f.read()
else:
    data = None

# EAFP (Easier to Ask for Forgiveness than Permission) — Pythonic
try:
    with open(path) as f:
        data = f.read()
except FileNotFoundError:
    data = None

The second is more concise and handles race conditions (file deleted between exists check and open call).

Exception propagation up the call stack

When an exception is raised, Python unwinds the stack, looking for a try/except that handles it.

Each frame in the traceback is one step in this unwinding.

The simplest form

Code Block
Python 3.13.2

as e binds the exception instance so you can inspect it (type, message, attributes).

Exception objects have attributes

Every exception is an object. You can inspect its type (type(e)), its message (str(e)), and sometimes custom attributes (like e.errno for OSError).

Catching multiple types

Code Block
Python 3.13.2

Parentheses group multiple exception types. The first matching except block wins.

Order matters

If you have multiple except blocks, put the most specific first. except Exception will catch everything, so if you put it first, more specific blocks below are unreachable.

else and finally

The full shape of the statement is:

try:
    ...                    # the risky operation
except SomeError as e:
    ...                    # runs if SomeError is raised
else:
    ...                    # runs only if NO exception was raised
finally:
    ...                    # always runs, error or not

else is useful when you only want code to run when the try block succeeded. finally is for cleanup that must happen either way (closing files, releasing locks, restoring state).

Code Block
Python 3.13.2

finally always runs

finally runs no matter what: success, exception, return, break, continue, even sys.exit(). It is the only way to guarantee cleanup. Use it for closing files, releasing locks, restoring state, etc.

Raising exceptions

Use raise to signal an error from your own code. Use a specific built-in type when one fits; otherwise create your own.

Code Block
Python 3.13.2

Which exception type to raise?

  • ValueError — Right type, wrong value (int("oops"), math.sqrt(-1))
  • TypeError — Wrong type entirely (len(5))
  • KeyError / IndexError — Missing dict key / list index
  • AttributeError — Missing attribute
  • FileNotFoundError / PermissionError — Filesystem trouble
  • RuntimeError — Generic "something else went wrong"
  • Custom exception — Domain-specific error (InsufficientFundsError, RateLimitExceeded)

raise ... from ... to preserve cause

When you wrap a lower-level error in a higher-level one, use from to keep the chain visible in the traceback.

Code Block
Python 3.13.2

Without from, the original ValueError is hidden. With from, the traceback shows both exceptions and their relationship.

raise from vs bare raise

  • raise NewError(...) from e — Wraps e in a higher-level error. Both appear in the traceback, with from marking the causal relationship.
  • raise NewError(...) — Replaces e with a new error. The original is lost (unless you manually include its message).
  • raise (no arguments) — Re-raises the current exception. Useful in except blocks when you want to log and re-raise.

Custom exception classes

Make your own exception types by subclassing Exception (or a more specific built-in). This lets callers catch your errors precisely.

Code Block
Python 3.13.2

Custom exceptions make your code self-documenting: except InsufficientFundsError is clearer than except ValueError (which could mean anything).

Exception hierarchy

Every exception inherits from BaseException. The ones you usually care about inherit from Exception (which itself inherits from BaseException). A few useful built-ins:

  • ValueError – right type, wrong value (int("oops"))
  • TypeError – wrong type entirely (len(5))
  • KeyError / IndexError – missing dict key / list index
  • AttributeError – missing attribute
  • FileNotFoundError / OSError – filesystem trouble
  • RuntimeError – generic, "something else went wrong"
  • StopIteration – an iterator is exhausted

Exception catches "every normal error". Do not catch BaseException casually; it includes KeyboardInterrupt and SystemExit, which you usually want to let through.

Code Block
Python 3.13.2

Never catch BaseException casually

except BaseException will catch KeyboardInterrupt (Ctrl+C) and SystemExit (from sys.exit()), making your program impossible to kill. Always catch Exception or more specific types.

Bare except: is almost always a bug

try:
    ...
except:        # catches everything, including KeyboardInterrupt
    pass

This silences errors you did not intend to handle and makes debugging miserable. Always be specific.

Bare except: antipattern

Never write except: without a type. It catches everything, including KeyboardInterrupt, SystemExit, and MemoryError. If you truly want to catch all normal errors, use except Exception:. But even that is usually too broad; prefer specific types.

EAFP vs LBYL philosophy

Python culture strongly prefers EAFP (Easier to Ask for Forgiveness than Permission) over LBYL (Look Before You Leap):

Code Block
Python 3.13.2

EAFP is often faster (one operation instead of two) and handles race conditions (state can change between check and use).

When to use LBYL

Use LBYL when the check is cheap and the exception is expensive (rare). Use EAFP when the happy path is common and exceptions are the exception. For most Python code, EAFP is idiomatic.

Challenges

Challenge
Python 3.13.2
Safer integer parsing

Define a function to_int(text, default=0) that returns int(text) if possible, otherwise default. It should not propagate any exception to the caller.

Challenge
Python 3.13.2
Withdraw with a custom exception

Define a class InsufficientFundsError that inherits from Exception, then define a function withdraw(balance, amount) that returns the new balance after withdrawing amount, or raises InsufficientFundsError if amount > balance. amount must be non-negative; raise ValueError otherwise.

Challenge
Python 3.13.2
Safe divide with default

Define a function safe_divide(a, b, default=None) that returns a / b if possible, otherwise default. Catch both ZeroDivisionError and TypeError.

Challenge
Python 3.13.2
Rate-limit retry decorator

Define a decorator retry_on_rate_limit(max_retries=3) that catches a custom RateLimitError exception and retries the function up to max_retries times. If all retries fail, re-raise the exception.

Define RateLimitError as a custom exception class.

For testing, assume a function that raises RateLimitError on the first n-1 calls and succeeds on the nth call.

Multiple choice questions

QuestionSelect one

Which block runs only when the try block raises no exception?

except

else

finally

The block after the try statement entirely

QuestionSelect one

What is wrong with except: (bare except)?

It is invalid syntax.

It catches KeyboardInterrupt and SystemExit, making the program hard to kill.

It only catches Exception, which is too broad.

It requires an as e clause.

QuestionSelect one

When should you use raise ... from e?

When you want to suppress the original exception.

When wrapping a lower-level exception in a higher-level one, to preserve the cause.

When re-raising the same exception.

When you want to catch multiple exception types.

QuestionSelect one

What does finally do?

Runs only if an exception is raised.

Runs only if no exception is raised.

Runs no matter what: success, exception, return, or even sys.exit().

Suppresses the exception.

QuestionSelect one

What is the EAFP philosophy?

Always check if a file exists before opening it.

Try the operation and catch the exception if it fails.

Never use exceptions; they are expensive.

Use assertions instead of exceptions.

QuestionSelect one

What happens if an exception is raised and not caught?

The exception is silently ignored.

Python prints a traceback and exits with a non-zero exit code.

The exception is logged but the program continues.

The exception is converted to a warning.

Many exceptions come from I/O. Files are next.

On this page