C.W.K.
Stream
Lesson 01 of 06 · published

try / except / else / finally — All Four Clauses

~20 min · try, except, else, finally

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The four-clause shape

Python's exception handling has four clauses: try, except, else, finally. Most code uses just try/except; the other two earn their keep in specific situations. else runs when try succeeded without raising. finally always runs, exception or not — used for cleanup that must happen.

Why a separate else clause

Putting code after the try body but inside the try block widens what except can catch — a bug there gets silently caught. Putting the same code in else means the except only catches what was actually risky, and the "everything went fine, here's the next step" logic is separate. Small clarification, real bug-prevention.

Catching multiple exception types

You can catch a tuple of types: except (ValueError, TypeError):. You can have multiple except clauses, checked in order — first match wins. You can also bind the exception object: except ValueError as e:.

Bare except is almost always wrong

except: with no type catches everything, including KeyboardInterrupt (Ctrl+C) and SystemExit. Code that should respond to those is silently swallowing them. Always prefer except Exception at the broadest, and ideally narrow further to the specific type you expect.

Principle: Catch the narrowest exception type that handles the failure mode you can recover from. Catching too broadly hides bugs you didn't expect; catching too narrowly leaves real failures unhandled. The right grain depends on the operation; default to specific types over Exception.

Code

try / except / else / finally — all four·python
def parse_int(s):
    try:
        result = int(s)
    except ValueError:
        print("could not parse")
        return None
    else:
        print("parsed successfully")
        return result
    finally:
        print("always runs (cleanup, logging, etc.)")

print(parse_int("42"))
# parsed successfully
# always runs (cleanup, logging, etc.)
# 42

print(parse_int("not a number"))
# could not parse
# always runs (cleanup, logging, etc.)
# None
Multiple except clauses — first match wins·python
def lookup(d, key):
    try:
        return int(d[key])
    except KeyError:
        return f"key {key!r} missing"
    except ValueError:
        return f"key {key!r} not an int"
    except TypeError:
        return f"d is not a dict"

print(lookup({"a": "42"}, "a"))      # 42
print(lookup({"a": "42"}, "b"))      # key 'b' missing
print(lookup({"a": "x"}, "a"))       # key 'a' not an int
print(lookup(None, "a"))             # d is not a dict

# Tuple of types — single except handles multiple
try:
    pass
except (KeyError, ValueError) as e:
    print(type(e).__name__, e)
Why else helps — narrowing what except sees·python
# WITHOUT else — risky
def bad(file_path):
    try:
        with open(file_path) as f:
            data = f.read()
            print("file size:", len(data))      # if this fails, except catches
            value = int(data)                    # this too
    except FileNotFoundError:
        print("missing file")
    except ValueError:
        print("bad number")
    # but a bug in print() or len() also gets caught — silently swallowed

# WITH else — except only catches the actual risky operations
def good(file_path):
    try:
        with open(file_path) as f:
            data = f.read()
    except FileNotFoundError:
        print("missing file")
        return
    else:
        print("file size:", len(data))
        value = int(data)                       # if THIS fails, NOT caught
        return value
Bare except — almost always wrong·python
# DON'T
try:
    risky()
except:                       # bare except
    pass                      # swallows EVERYTHING including Ctrl+C

# DO — Exception is the broadest reasonable
try:
    risky()
except Exception as e:
    print("got:", type(e).__name__, e)

# BETTER — narrow to what you actually expect
try:
    risky()
except (ValueError, KeyError) as e:
    print("expected failure:", e)
# Other exceptions propagate up — that's correct

External links

Exercise

Write a function safe_divide(a, b) that returns a / b, but: catches TypeError (returns the string "bad types"), catches ZeroDivisionError (returns float('inf') if a > 0, float('-inf') if a < 0, 0.0 if a == 0), uses else to print 'success' before returning, and finally to print 'attempted'. Test with at least four input combinations including normal, divide by zero, and wrong types.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.