Command Palette

Search for a command to run...

Hectal
PHASE 7Intermediate ~28 min· topic 1 of 12

Topic 7.1

Exceptions: try, except, else, finally

In one line

An exception is Python's way of saying that something went wrong at this exact line. try/except lets you catch a specific kind of failure and carry on, else runs only when nothing failed, and finally always runs for cleanup.

Think of it like this

A waiter carrying a tray. If a glass slips, the waiter does not stop the whole restaurant. They shout "dropped glass!", someone comes with a cloth, and service continues. An exception is that shout: a piece of code signals "something went wrong here", and the nearest code that knows how to deal with it takes over. If nobody knows how, the shout reaches the manager (Python itself) and the program stops with a report called a traceback.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Exception
An object that Python creates when something goes wrong. It carries the kind of error and a message.
Raise
To signal an exception. Python raises one for you when code fails; you can raise your own.
Catch (handle)
To write code that runs when a certain exception happens, so the program does not crash.
try / except
The statement that runs code and jumps to a handler if a matching exception is raised.
else (on try)
A block that runs only when the try block raised no exception.
finally
A block that always runs at the end, whether or not there was an error. Used for cleanup.
Traceback
The report Python prints for an uncaught exception, listing the chain of calls that led to it.
EAFP / LBYL
Two styles: try first and handle failure (Python's usual style) versus check first, then act.

Step by step

01Without a handler the program stops

This program converts a piece of text to a number. int("4x2") cannot succeed, so Python raises a ValueError and the program stops at that line. The second print never runs.

main.pywhole filepython
text = "4x2"
number = int(text)
print("Parsed:", number)

02Catch the one error you expect

Wrap the risky line in try: and name the error after except. If int(text) raises ValueError, Python skips the rest of the try block and runs the handler. After the handler the program carries on normally.

Notice we caught ValueError only. If a different error appeared (say a NameError from a typo), it would still show up loudly, which is what we want.

main.pywhole filepython
text = "4x2"
try:
    number = int(text)
except ValueError:
    number = 0
    print("Not a whole number, using 0")
print("Number is", number)

03The exception object: as e

except ValueError as e: gives the exception object a name. Printing it shows the message Python attached, and type(e).__name__ gives the class name. Use the message in logs or in an error shown to the user. The name e exists only inside the handler and is deleted when the handler ends.

main.pywhole filepython
try:
    int("4x2")
except ValueError as e:
    print("Class:", type(e).__name__)
    print("Message:", e)

04Several handlers, and the family tree

Recall from Topic 5.1: def makes a function and return hands a value back. The function below uses both.

You can stack except blocks for different errors, or list several classes in a tuple to share one handler. Python checks the handlers from top to bottom and runs the first one that matches. Put the most specific class first: if except Exception came first, it would catch everything and the specific handlers below it could never run.

main.pywhole filepython
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return float("inf")
    except (TypeError, ValueError) as e:
        print("Bad input:", e)
        return None
Several handlers, and the family treediagram
Rendering diagram…

05else and finally

else is for code that should run only on success. Keeping it out of the try block means a bug in that code is not accidentally caught by your except. finally runs last, always. The order is: try, then (except if it failed, or else if it succeeded), then finally.

finally even runs when the try block uses return: Python finishes the finally first, then returns. Never put a return inside finally, because it silently replaces the real result and cancels any exception in flight.

else and finallydiagram
Rendering diagram…

06EAFP versus LBYL

Both functions below read a price from a dictionary. The LBYL version looks first. The EAFP version just tries and handles KeyError. EAFP does one lookup instead of two and stays correct even if the data changes between the check and the use, which matters with files and shared data.

Use LBYL when failure is common and the check is the natural logic (if not items:). Use EAFP when failure is the exception to the rule.

main.pywhole filepython
prices = {"tea": 20, "coffee": 35}

# LBYL: look before you leap
if "juice" in prices:
    cost = prices["juice"]
else:
    cost = 0

# EAFP: ask forgiveness
try:
    cost = prices["juice"]
except KeyError:
    cost = 0

Try it yourself

  1. 1

    Predict the order

    Before running the first example, write down the order of the printed lines for "42" and for "4x2". Then run it. Which line is the only one that appears in both runs?

  2. 2

    Swap the handlers

    In the third example, move except Exception above except ZeroDivisionError, and run it. Which value now gets the wrong message, and why? This is the "most specific first" rule in action.

  3. 3

    Catch a different error

    In the fourth example, add the value 3.7 to the list. Does int("3.7") work? Add the value None as well and see which exception appears. Then decide whether to catch it too.

Code & diagrams

try, except, else, finally in order New tab

Run it and read the order of the lines: for a good value the else block runs; for a bad value the except block runs. The finally line appears both times.

Sign in to run this example in your browser.

Expected output

Trying 42
  '42' parsed fine
  finished with '42'
  result: 42
Trying 4x2
  '4x2' is not a whole number
  finished with '4x2'
  result: None
The exception family tree New tab

Every class on the right of an arrow is a parent. Catching a parent catches the child.

Sign in to run this example in your browser.

Expected output

ZeroDivisionError -> ArithmeticError -> Exception -> BaseException -> object
KeyError -> LookupError -> Exception -> BaseException -> object
FileNotFoundError -> OSError -> Exception -> BaseException -> object
KeyboardInterrupt -> BaseException -> object
Caught as LookupError: KeyError 'missing'
Order of handlers matters New tab

Python runs the first handler that matches. Try swapping the two except blocks to see the specific one never run.

Sign in to run this example in your browser.

Expected output

4 -> 25.0
0 -> cannot divide by zero
'x' -> needs a number
None -> needs a number
Retry a flaky step, skip a bad item New tab

A tiny version of what a data pipeline does: one bad record is reported and skipped, and the rest are still processed.

Sign in to run this example in your browser.

Expected output

Total of good records: 70
Skipped: ['abc', '']

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Catch the wrong class

Handle a KeyError but let a ValueError happen.

main.pywhole filepython
try:
    number = int("abc")
except KeyError:
    number = 0
print(number)
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 2, in <module>
number = int("abc")
ValueError: invalid literal for int() with base 10: 'abc'

Break #2

A bare except hides a typo

Wrap a block in except: and misspell a name inside it.

main.pywhole filepython
total = 10
try:
    print("Total is", totl)
except:
    print("Something went wrong")
terminal
$ python main.py
── what you'll see ──
Something went wrong

Myth vs fact

Myth

Exceptions are for rare disasters, and good code avoids them.

Fact

In Python exceptions are an ordinary control tool. Iterating with a for loop even ends internally with a StopIteration exception. Catching KeyError or ValueError is everyday code.

Myth

except Exception: is safe because it catches everything.

Fact

Catching everything also catches your own bugs (typos, wrong types) and turns them into silent wrong behaviour. Catch what you can handle.

Myth

finally only runs when there is an error.

Fact

finally runs every time: on success, on a handled error, on an unhandled error, and even when the try block returns.

Myth

try makes code slower, so avoid it.

Fact

In current CPython a try block that raises nothing costs almost nothing. Raising and catching is slower than an if, so use LBYL when failure is the normal case, but never avoid try for speed in ordinary code.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Python 3.11 introduced "zero-cost" exception handling: entering a try block adds no instructions on the normal path. The handler lookup uses a table consulted only when something is raised. That is why EAFP is cheap when failures are rare.

  • ▸

    except clauses match with isinstance against the raised exception, so you can catch a tuple of classes, and you can build custom hierarchies (Topic 7.2) so callers catch one parent class for a whole family of problems.

  • ▸

    A caught exception keeps references to the traceback and, through it, to every local variable of the frames involved. That is why e is deleted at the end of the handler: to break reference cycles that would keep large objects alive. If you need the exception later, store it under another name deliberately, or store only its message.

  • ▸

    In asyncio code, never swallow asyncio.CancelledError (a BaseException subclass since Python 3.8). Doing so breaks cancellation and timeouts. This is one more reason not to write bare except: blocks.

  • ▸

    A return, break or continue inside finally discards any exception that is in flight and overrides the value being returned. It is legal, so for years it was a silent trap; Python 3.14 (PEP 765) now emits a SyntaxWarning for it. An exception raised inside a handler or inside finally replaces the one being handled (the old one stays reachable as __context__, Topic 7.2). To ignore one specific, expected error, contextlib.suppress(FileNotFoundError) says it more clearly than an empty except block.

Remember this

  1. 1

    When Python hits a problem it cannot continue past, for example int("abc") or 10 / 0, it raises an exception. It stops running the current line. Then it searches upward, from the current function to the function that called it, and so on, for code that catches that kind of exception. If it finds none, the program ends and prints the traceback you met in Topic 0.7. Raising and catching is not shameful. It is the normal way Python code reports problems.

  2. 2

    The tool for catching is the try statement. Code that might fail goes in the try: block. Each except SomeError: block below it says: if this kind of exception is raised inside the try, jump here instead of crashing. Only the matching except runs, and after it the program continues with the next statement after the whole try.

  3. 3

    Exceptions are classes arranged in a family tree. (A class is a blueprint for making objects. Phase 8 teaches classes, so for now just read "class" as "a kind of thing, with a name".) BaseException is the root. Below it, Exception is the parent of almost every error you will meet: ValueError, TypeError, KeyError (a kind of LookupError), ZeroDivisionError (a kind of ArithmeticError), FileNotFoundError (a kind of OSError). Catching a parent catches all of its children, so except LookupError catches both KeyError and IndexError. KeyboardInterrupt and SystemExit sit directly under BaseException, outside Exception, so a normal except Exception does not swallow Ctrl+C.

  4. 4

    Catch specific errors, never everything. A bare except: (no class named) or an except Exception: that only hides the problem turns a loud, findable bug into a silent wrong answer. The rule of thumb: catch an exception only if you know what went wrong and have a sensible response (use a default, retry, show a friendly message, skip this item). Otherwise let it travel upward to code that can decide.

  5. 5

    The full shape has two more clauses. else: runs only if the try block finished without an exception, which keeps the risky part small and the follow-up work outside it. finally: runs always: after success, after a handled error, even if the try block does return. Use finally for cleanup that must happen no matter what (closing a connection, removing a temporary file). Topic 7.4 shows with, which is the neater way to do most cleanup.

  6. 6

    Python programmers usually follow EAFP: "Easier to Ask Forgiveness than Permission". Just try the operation and handle the failure. The opposite style, LBYL ("Look Before You Leap"), checks conditions first (if key in data:). EAFP is often shorter. It also avoids a race: a file you checked can vanish in the moment before you open it. LBYL is clearer when the check is cheap and is the natural rule ("if the list is empty, say so"). This matters for AI code: a pipeline that processes 10,000 documents should catch the failure for document 7,312, log it, and carry on with the rest.

Explain it without notes

01

What happens, step by step, when an exception is raised and no handler is found in the current function?

02

Why is a bare except: considered bad practice, and what should you do instead?

03

What do else and finally do in a try statement, and why is else not just 'code after the try'?

04

What are EAFP and LBYL? When would you pick each?

Practice

01

Write safe_int(text, default=0) that returns int(text), or default if the text is not a whole number. Test it with "12", "x" and "".

02

Write average(numbers) that returns the average of a list, or None for an empty list. Use try/except ZeroDivisionError.

03

Write a loop over ["3", "0", "x", "5"] that prints 10 / int(item) for each, printing a different friendly message for ZeroDivisionError and for ValueError.

Trade-offs

  • ↔

    Catching close to the source versus far away. Catching near the failure lets you recover precisely (use a default). Catching far away (one handler at the top) keeps the code clean but can only report and stop. Most programs do both.

  • ↔

    Exceptions versus return codes. Returning None or -1 for failure lets callers forget to check and carry on with bad data. An exception cannot be ignored by accident. The cost is that a caller must know which exceptions a function can raise, so document them.

Done when you can

  • Done when you can write try/except/else/finally and say in which order the blocks run.

  • Done when you can name the exception parent classes Exception, LookupError and OSError and what they cover.

  • Done when you can explain why a bare except: is dangerous and what to write instead.

  • Done when you can read except A as e and use str(e) and type(e).__name__.

  • Done when you can choose between EAFP and LBYL for a given problem.