A customer types 'three' where the form expects 3, and the till freezes mid-queue. The failure was predictable; the recovery should be too. Which exceptions deserve a catch, and which should still stop the program?
Errors are information. Some are bugs you should fix; others are expected, such as invalid user input or a missing field. try and except let you handle the expected ones without hiding everything else. You will also handle one bad value without stopping the loop and re-raise with context.
Catch a specific exception
Put the risky line inside try. Catch the exception you expect, such as ValueError from int(). Avoid a bare except, because it also hides programming mistakes you need to see.
text = "12"
try:
value = int(text)
except ValueError:
value = 0
print(value)A safe conversion
Different errors, different fixes
A missing dictionary key raises KeyError. Converting bad text raises ValueError. Catching the wrong type means the handler will not run. Name the exception you can actually recover from.
order = {}
try:
price = order["price"]
except KeyError:
price = 0
print(price)A missing field
finally and else
else runs when try finishes without an exception. finally always runs, whether the try succeeded or failed. Use finally for cleanup such as closing a resource.
try:
print("working")
except ValueError:
print("failed")
else:
print("ok")
finally:
print("done")The full try structure
Catch only what you can handle
Catch only the exception you can handle. A bare except also catches typos and programming errors, which makes debugging harder. In a loop, catch ValueError for one bad value and continue with the rest.
raw_values = ["3", "many", "5"]
numbers = []
for raw in raw_values:
try:
numbers.append(int(raw))
except ValueError:
print("skipping:", raw)
print(numbers)Recover from one bad row
Add context before re-raising
When re-raising, add context without losing the original cause. raise ... from error keeps the chain. Include the bad value with repr() so invisible whitespace is visible.
def parse_price(raw):
try:
return float(raw)
except ValueError as error:
raise ValueError(f"invalid price: {raw!r}") from error
for raw in ["4.50", "free"]:
try:
print(parse_price(raw))
except ValueError as error:
print(error)Keep the cause and add the bad input
Catch the specific error
A bare except catches everything, including typos and interruptions you cannot fix. Catch the exception you expect, such as ValueError from int(). Use else for code that should run only after success, and finally for cleanup that must always run. If you cannot explain what you will do with an error, let it propagate.
raw = "three"
try:
qty = int(raw)
except ValueError:
print("not a number")
else:
print("quantity", qty)
finally:
print("done")Specific handling, clear cleanup
Key points
- Catch the specific exception you can handle.
- ValueError covers bad conversions; KeyError covers missing dictionary keys.
- else runs after a successful try; finally always runs.
- Do not use a bare except to silence every bug.
- Catch the narrowest exception and add context when re-raising.
- Catch the exception you can handle; let unexpected ones surface.
Common mistakes
- Catching Exception when ValueError is the only expected failure.
- Putting too much code in try, which hides where the error started.
- Assuming print in except is enough; the caller may need a fallback value.
- Using a bare except that also hides programming errors.
- Using a bare except and hiding a typo in the try block.
Try converting a text value to an integer, use 0 as the fallback, and print the result.
Try it yourself
Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.
Practice this lesson
12 questions with immediate explanations.