Chapter 4 · Mid-level Developer

Error handling: try / except / raise

Things go wrong — bad input, a missing file, a network timeout, division by zero. By default an exception crashes the program with a traceback. try/except lets you catch that and respond instead of dying.

Code that might fail goes in the try block; the except block runs only if a matching exception is raised. The golden rule: catch the narrowest exception you can. except ValueError: catches a conversion failure and nothing else; bare except: (or except Exception:) swallows *everything*, including typos and bugs you actually want to see.

Two optional clauses round it out:

  • else: — runs only if the try block did not raise (the success path).
  • finally: — runs no matter what, error or not (cleanup: close a file, release a lock).

You can also throw your own with raise SomeError('message') — useful for signalling that input is invalid in a way callers can catch. For domain-specific failures, define a custom exception by subclassing Exception.

Syntax

try:
    risky()
except ValueError as e:      # catch a SPECIFIC type
    handle(e)
except (KeyError, TypeError): # catch several
    ...
else:
    # ran only if no exception
    ...
finally:
    # always runs (cleanup)
    ...

raise ValueError('bad input')   # throw one yourself

class ConfigError(Exception):   # custom exception
    pass

Worked examples

Catch a specific exception, return a fallback

def safe_int(value):
    try:
        return int(value)
    except ValueError:
        return None

print(safe_int('42'))    # 42
print(safe_int('nope'))  # None

Raise your own, with a clear message

def safe_divide(a, b):
    if b == 0:
        raise ValueError('Cannot divide by zero')
    return a / b

print(safe_divide(10, 2))   # 5.0
# safe_divide(5, 0)  →  ValueError: Cannot divide by zero

else and finally

try:
    x = int('5')
except ValueError:
    print('bad input')
else:
    print(f'parsed {x}')   # runs (no error)
finally:
    print('done')          # always runs
# Output:
# parsed 5
# done

Common mistakes & gotchas

Bare except hides real bugs

except: or except Exception: catches *everything* — including NameError from a typo or a KeyboardInterrupt. A bug then looks like normal behaviour and you debug for hours. Catch the specific exception you expect, and let the rest surface.

Swallowing an error silently

except ValueError: pass makes the failure vanish with no trace. If you catch an error, do something meaningful — return a fallback, log it, or re-raise. Silent pass is how data corruption goes unnoticed.

try blocks should be small

Wrapping ten lines in one try makes it unclear which line you expected to fail, and a different error gets caught by accident. Keep the try around just the risky operation; put the rest outside it.

Why it matters

The difference between a script and a robust program is what happens when things go wrong. Professional code anticipates failure — bad input, missing files, flaky networks — and handles it cleanly instead of dumping a traceback on the user. Narrow, deliberate `try`/`except` is the backbone of that resilience.