try/except with specific exception types
Overview
The `try/except` block is how Python handles errors (exceptions) without crashing the program. You put risky code in the `try` block. If an error occurs, Python stops executing the `try` block immediately and jumps to the `except` block. You should always catch specific exception types (like `ValueError` or `KeyError`) rather than using a bare `except:`. A bare `except:` catches absolutely everything, including `SystemExit` and `KeyboardInterrupt` (`Ctrl+C`). This makes your program impossible to stop and hides unrelated bugs. try: user_age = int('twenty') except ValueError: user_age = 0 You can handle multiple specific exceptions in a single block by grouping them in a tuple. try: result = 10 / int(user_input) except (ValueError, ZeroDivisionError): result = 0 There is a sharp edge when capturing the exception object using `as`. Python completely deletes the bound variable at the end of the `except` block. try: 1 / 0 except ZeroDivisionError as e: pass print(e) # Raises NameError: name 'e' is not defined Python intentionally deletes the exception variable to prevent reference cycles and memory leaks. If you need the exception object later, you must assign it to a different variable name. This memory management behavior is covered fully in variables-and-scope.
In the real world, things fail: networks drop, users send `'abc'` instead of an age, and files go missing. `try/except` lets your program recover gracefully, logging the error or returning a `400 Bad Request` instead of terminating entirely. Catching specific exceptions ensures you only handle the exact error you anticipate. This lets unexpected bugs crash loudly so they can be fixed.
Where used: Guarding API endpoints against malformed input, Retrying failed database transactions, Safely parsing third-party JSON
Why learn this
- Preventing your entire application from crashing due to one bad user request
- Understanding why bare `except:` is considered a severe anti-pattern in professional codebases
- Capturing the exception object (e.g. `except ValueError as e`) to log its message
Code walkthrough
def parse_age(value):
try:
age = int(value)
print(f"Age is {age}")
except ValueError as e:
print(f"Invalid input: {e}")
parse_age('twenty')
Focus: The `int('twenty')` call raises a `ValueError`, causing execution to instantly jump to the `except ValueError as e` block, preventing a crash.
Aha moment
import sys
try:
sys.exit("Quitting time!")
except:
print("I caught the exit signal! Not quitting.")
print("Program is still running...")
Prediction: What happens when you run this code?
Common guess: The program exits immediately because sys.exit() is called.
A bare `except:` catches literally everything, including the `SystemExit` exception raised by `sys.exit()`. This completely breaks standard application shutdown mechanisms, proving why you must always specify the exception type.
Common mistakes
- Using a bare `except:`: Writing just `except:` catches `BaseException`, which includes `KeyboardInterrupt`. If your script loops with a bare `except:`, you won't be able to stop it with `Ctrl+C`. Always use at least `except Exception:` or, ideally, the specific error like `except KeyError:`.
- Catching exceptions too broadly: Wrapping a massive block of code in `try... except ValueError:` makes it hard to know which line actually failed. Keep `try` blocks as small as possible.
Glossary
- bare except:
- An exception handler that catches absolutely every type of error, which is generally considered bad practice. Example: `except:`.
- SystemExit
- A special exception that is raised when a program tries to shut itself down or stop running. Example: `raise SystemExit`.
Recall questions
- Why is it dangerous to use a bare `except:` without specifying an exception type?
- How do you capture the actual exception object inside the `except` block so you can log its error message?
- How can you catch multiple specific exceptions in a single `except` block?
- What happens if you try to access an exception variable (bound with `as`) outside of its `except` block?
Understanding checks
What does this print?
`Handled`
`int("hello")` raises a `ValueError`, so execution jumps to the `except ValueError` block and prints `'Handled'`.
A developer writes a script that wraps all logic in a single `try:` block with a bare `except:` at the bottom to prevent crashes. Why is this a bad idea?
It catches everything, including `SystemExit` and `KeyboardInterrupt` (`Ctrl+C`), making the program un-stoppable and hiding actual bugs.
A bare `except:` catches `BaseException`. You should catch specific exception types (like `ValueError`) to only handle anticipated errors.
Practice tasks
Safe dictionary lookup
The `get_user_name` function currently works for valid user dictionaries. Modify it to handle missing `'name'` keys by catching a `KeyError` and returning `'Anonymous'` instead of crashing.
Challenge
Robust API handler
Write a function `handle_request(payload)` that tries to access `payload['user']['age']` and convert it to `int`. Catch `KeyError` (print `'Missing field'`) and `ValueError` (print `'Invalid format'`) separately.
Continue learning
Previous: Defining functions and return values
Previous: Type conversion: implicit vs explicit casting
Next: else and finally blocks
Next: Raising exceptions with raise
Next: Custom exception classes