Lesson 09 · Beginner · 19 min

Python Error Handling

Catch predictable failures, show a useful fallback, and keep the program running.

try/exceptValueError and KeyErrorfinally

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.
Your turn

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.

In-browser PythonRuns locally · no account
First run downloads Pyodide from a CDN. Use Run code after editing.

Practice this lesson

12 questions with immediate explanations.

See all 12 answersStart practice
← FunctionsJSON data →