Python Tutorial
Handling Multiple Exceptions, else and finally
Real code can fail in several ways at once: a file might be missing, contain invalid numbers, or the network might time out. Python's try statement lets you handle each error type differently, run code only when nothing failed (else), and always run cleanup (finally). Python 3.11 also added exception groups for handling several errors raised together.
This lesson covers catching multiple exceptions, the order of except blocks, exception hierarchies, else, finally, and except* with ExceptionGroup.
Several except Blocks
A try can have many except clauses; Python uses the first one whose type matches (including subclasses). Put specific exceptions before general ones — FileNotFoundError before OSError, and a broad Exception (if any) last. Bind the exception with as e to inspect its message.
Catching Several Types in One Clause
Use a tuple to handle different exceptions the same way: except (ValueError, TypeError) as e:. Avoid bare except:, which also catches KeyboardInterrupt and SystemExit.
else and finally
The else block runs only if the try block raised nothing — put code there that should run on success but whose own errors you do not want caught by the except clauses. The finally block always runs: after success, after a handled error, after an unhandled error, and even after return — ideal for releasing resources (though with is usually cleaner).
Exception Groups and except*
When several independent tasks fail at once (for example in asyncio.TaskGroup), Python raises an ExceptionGroup. except* ValueError handles all ValueErrors in the group while letting other types continue to other except* clauses.
Examples
Different handling for different errors
def average_from(values, divisor_text):
try:
numbers = [int(v) for v in values]
divisor = int(divisor_text)
return sum(numbers) / divisor
except ZeroDivisionError:
return "cannot divide by zero"
except ValueError as e:
return f"bad number: {e}"
except TypeError:
return "values must be a list of strings"
print(average_from(["10", "20"], "2"))
print(average_from(["10", "20"], "0"))
print(average_from(["10", "x"], "2"))
print(average_from(None, "2"))
15.0
cannot divide by zero
bad number: invalid literal for int() with base 10: 'x'
values must be a list of strings
One clause for several types, and why order matters
def parse(value):
try:
return int(value)
except (ValueError, TypeError) as e:
return f"{type(e).__name__}: cannot parse {value!r}"
print(parse("7"), "|", parse("seven"), "|", parse(None))
def read_config(path):
try:
with open(path) as f:
return f.read()
except FileNotFoundError: # specific first
return "config missing, using defaults"
except OSError as e: # more general afterwards
return f"OS error: {e}"
print(read_config("does_not_exist.ini"))
print(issubclass(FileNotFoundError, OSError))
7 | ValueError: cannot parse 'seven' | TypeError: cannot parse None
config missing, using defaults
True
try / except / else / finally flow
def divide(a, b):
print(f"-- divide({a}, {b})")
try:
result = a / b
except ZeroDivisionError:
print("except: division by zero")
return None
else:
print("else: success, result =", result)
return result
finally:
print("finally: always runs")
divide(10, 2)
divide(1, 0)
-- divide(10, 2)
else: success, result = 5.0
finally: always runs
-- divide(1, 0)
except: division by zero
finally: always runs
ExceptionGroup and except*
def validate(order):
errors = []
if order.get("qty", 0) <= 0:
errors.append(ValueError("quantity must be positive"))
if "@" not in order.get("email", ""):
errors.append(ValueError("invalid email"))
if not isinstance(order.get("price"), (int, float)):
errors.append(TypeError("price must be a number"))
if errors:
raise ExceptionGroup("order is invalid", errors)
try:
validate({"qty": 0, "email": "asha", "price": "free"})
except* ValueError as group:
for e in group.exceptions:
print("value problem:", e)
except* TypeError as group:
for e in group.exceptions:
print("type problem:", e)
value problem: quantity must be positive
value problem: invalid email
type problem: price must be a number
Common Mistakes
- Catching Exception first, so specific handlers below it never run.
- Using bare except:, which also swallows KeyboardInterrupt and SystemExit.
- Putting too much code inside try, catching errors you did not intend to handle — use else.
- Returning from finally, which silently discards exceptions.
Key Points to Remember
- Python runs the first matching except clause; order from specific to general.
- Catch several types with a tuple: except (A, B) as e.
- else runs only on success; finally always runs.
- ExceptionGroup + except* handle multiple simultaneous errors (3.11+).
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.