Control flow · Lesson 6 of 18 · 16 min
Exceptions
Errors as values you can catch, raise, and design around.
- Catch specific exceptions
- Use else/finally correctly
- Raise with intent, not as flow for the happy path
EAFP, not LBYL
Python style prefers Easier to Ask Forgiveness than Permission: try the operation, catch the specific error. Looking before you leap (`if key in d`) races in concurrent code and often duplicates work the operation will do anyway.
Always catch the narrowest type you can handle. `except Exception` is a last resort. Bare `except:` also catches `KeyboardInterrupt` and `SystemExit` — almost never what you want.
def parse_int(text):
try:
value = int(text)
except ValueError as exc:
return f"bad: {exc}"
else:
return f"ok: {value}"
finally:
pass
print(parse_int("42"))
print(parse_int("nope"))The clauses have jobs
`except` handles failure. `else` runs only if no exception was raised in `try` — a good place for code that should not catch its own errors. `finally` always runs, whether you return, raise, or succeed, which is why it is the right home for cleanup when a context manager is not available.
`raise` without an argument inside an except block re-raises the active exception. `raise NewError('msg') from exc` chains exceptions so the traceback shows both the cause and the new meaning you attached.
class ConfigError(Exception):
pass
def load_port(raw):
try:
return int(raw)
except ValueError as exc:
raise ConfigError(f"port must be an int, got {raw!r}") from exc
try:
load_port("abc")
except ConfigError as exc:
print(type(exc).__name__, exc)
print("cause:", type(exc.__cause__).__name__)Check yourself
Quiz
1.The else clause of try runs when:
2.Why is `except:` (bare) a problem?
3.`raise New from exc` stores the original on:
Practice
Exercise
Write `safe_div(a, b)` that returns a / b as a float. If b is 0, return None instead of raising. Any other error should still raise.
Tutor
Ask a question about this lesson. Answers are generated on demand and capped so the session stays focused.