try / except / finally
Updated March 26, 2026 · Keywords
exceptions error-handling stdlib keywords
Description
The try, except, else, and finally blocks handle exceptions in Python. Use them to catch errors, inspect exception objects, and guarantee cleanup code runs regardless of whether an exception occurred.
Syntax
try:
# code that may raise an exception
except <ExceptionType>:
# runs if the specified exception is raised
except (<ExceptionType1>, <ExceptionType2>):
# catches multiple exception types in one clause
except <ExceptionType> as <name>:
# binds the exception instance to a name for inspection
except:
# bare except — catches everything (use sparingly)
else:
# runs only if no exception was raised in the try block
finally:
# always runs, regardless of whether an exception occurred
```python
## try Block
The `try` block contains code that may raise an exception. Execution proceeds sequentially until the block completes without error, or an exception is raised and matched by an `except` clause.
## except with Exception Type
Catch a specific exception type:
```python
def parse_age(input_str):
try:
age = int(input_str)
return {"age": age}
except ValueError:
return {"error": "Not a valid integer"}
print(parse_age("25")) # {'age': 25}
print(parse_age("nope")) # {'error': 'Not a valid integer'}
# Output:
# {'age': 25}
# {'error': 'Not a valid integer'}
```python
## except as
Bind the exception instance to a name to inspect its attributes:
```python
def read_file(path):
try:
with open(path) as f:
return f.read()
except FileNotFoundError as e:
return {"error": f"File not found: {e.filename}"}
except PermissionError as e:
return {"error": f"Permission denied: {e}"}
except OSError as e:
return {"error": f"OS error: {e}"}
print(read_file("/nonexistent/file.txt"))
# Output: {'error': 'File not found: /nonexistent/file.txt'}
```python
## except with Multiple Types
Catch several exception types in a single clause using a tuple:
```python
def safe_divide(a, b):
try:
return {"result": a / b}
except (ZeroDivisionError, TypeError) as e:
return {"error": f"Cannot divide: {e}"}
print(safe_divide(10, 2)) # {'result': 5.0}
print(safe_divide(10, 0)) # {'error': 'Cannot divide: division by zero'}
print(safe_divide(10, "x")) # {'error': 'Cannot divide: unsupported operand type(s) for /'}
# Output:
# {'result': 5.0}
# {'error': 'Cannot divide: division by zero'}
# {'error': 'Cannot divide: unsupported operand type(s) for /'}
```python
## else Block
The `else` block runs only if no exception was raised in the `try` block. It executes before `finally`:
```python
import json
def parse_json(raw):
try:
data = json.loads(raw)
except json.JSONDecodeError:
return {"error": "Invalid JSON"}
else:
# runs only if json.loads succeeded
return {"data": data}
finally:
print("parse_json called")
print(parse_json('{"key": "value"}'))
print(parse_json("broken"))
# Output:
# parse_json called
# {'data': {'key': 'value'}}
# parse_json called
# {'error': 'Invalid JSON'}
```python
## finally Block
The `finally` block always executes — even after `return`, `break`, or `continue`:
```python
def read_config(path):
f = None
try:
f = open(path)
return {"config": f.read()}
except FileNotFoundError:
return {"config": {}}
finally:
# guaranteed to run, even after return
if f is not None:
f.close()
# File is closed whether the try succeeds or FileNotFoundError is raised
```python
## Bare except
A bare `except` catches everything, including `KeyboardInterrupt`, `SystemExit`, and `GeneratorExit` — which do not inherit from `Exception`. This is generally discouraged because it masks program-exit signals and makes debugging harder.
```python
try:
something()
except:
handle_error() # catches BaseException and all subclasses
```python
A common acceptable pattern is logging and re-raising, which satisfies linters:
```python
except:
logger.exception("Unexpected error")
raise
```python
## raise Statement
### Raising an Exception
```python
raise ValueError("Description of the problem")
raise ValueError() # no message
```python
### Re-raising
Inside an `except` block, `raise` with no argument re-raises the currently caught exception:
```python
try:
data = risky_call()
except SomeError:
# log it
raise # re-raises SomeError, preserving the original traceback
```python
### Exception Chaining with `from`
Chain a new exception to the original using `from`:
```python
import json
class ConfigError(Exception):
pass
def load_config(path):
try:
with open(path) as f:
return json.load(f)
except FileNotFoundError:
raise ConfigError(f"Config file '{path}' does not exist") from None
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in '{path}' at line {e.lineno}") from e
try:
load_config("config.json")
except ConfigError as e:
print(f"Config error: {e}")
if e.__cause__:
print(f"Caused by: {e.__cause__}")
# Output (if config.json contains invalid JSON):
# Config error: Invalid JSON in 'config.json' at line 3
# Caused by: 3: Expected ',' or ']', ...
```python
The `from None` form suppresses the implicit `__context__`, which is useful when you want a clean traceback.
## Exception Groups (Python 3.11+)
### Raising an ExceptionGroup
```python
raise ExceptionGroup("multiple errors", [
ValueError("invalid value"),
TypeError("wrong type"),
])
```python
### except* Syntax
The `except*` clause extracts matching exceptions from an `ExceptionGroup`:
```python
def validate(item):
if not isinstance(item, int):
raise TypeError(f"Expected int, got {type(item).__name__}")
if item < 0:
raise ValueError("must be non-negative")
def validate_all(items):
errors = []
for item in items:
try:
validate(item)
except (ValueError, TypeError) as e:
errors.append(e)
if errors:
raise ExceptionGroup("validation failed", errors)
try:
validate_all([1, 2, "three", -4, 5])
except* ValueError as vg:
print(f"ValueErrors: {[str(e) for e in vg.exceptions]}")
except* TypeError as tg:
print(f"TypeErrors: {[str(e) for e in tg.exceptions]}")
# Output:
# TypeErrors: ['Expected int, got str']
# ValueErrors: ['must be non-negative']
```python
`BaseExceptionGroup` can hold any exception (including `KeyboardInterrupt`). `ExceptionGroup` only holds `Exception` subclasses.
## Exception Hierarchy
```python
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── StopIteration
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── OSError
│ ├── FileNotFoundError
│ ├── PermissionError
│ ├── ConnectionError
│ └── TimeoutError
├── ValueError
├── TypeError
├── RuntimeError
├── NameError
└── ...
```python
`BaseException` is the root. Four direct children — `SystemExit`, `KeyboardInterrupt`, `GeneratorExit`, and `BaseException` itself — do not inherit from `Exception`. This is why a bare `except` catches them.
## Performance
- **No exception raised**: Near-zero overhead. CPython registers `finally` at try-entry time.
- **Exception raised and caught**: Roughly 3–10× slower than an equivalent `if` check, but still fast in absolute terms. Use `try`/`except` for control flow when errors are genuinely exceptional.
- **Exception raised and not caught**: Slower still, as the stack unwinds.
Use `try`/`except` when the error condition is rare and unavoidable. When errors are frequent, prefer explicit `if` checks.
## Common Patterns
### Suppress and continue
```python
try:
config = json.loads(user_config)
except (json.JSONDecodeError, FileNotFoundError):
config = {} # use defaults on failure
```python
### Guaranteed cleanup with lock
```python
lock = threading.Lock()
lock.acquire()
try:
shared_resource += 1
finally:
lock.release() # always runs, even after return
```python
### Retry with else
```python
for attempt in range(3):
try:
result = unreliable_call()
except TransientError:
continue
else:
return result # success — exits loop
else:
raise RuntimeError("All retries failed")
```python
## See Also
- [`except`](/reference/keywords/except-keyword/) — catch and handle specific exception types
- [`raise`](/reference/keywords/raise-keyword/) — raise exceptions with optional chaining
- [`error-handling`](/guides/error-handling/) — patterns for robust error handling in Python
- [`contextlib`](/reference/modules/contextlib-module/) — utilities like `suppress()`, `contextmanager`, and `closing()`