LESSON 18 · NEXT STEPS18 min

Exceptions

Catch expected runtime errors and return useful messages without hiding unrelated bugs.

Video coming to YouTube

Work through the full lesson and run its code while the video is being prepared for publication.

Understand the concept

A syntax error prevents Python from understanding a program. An exception happens while valid code runs, such as converting 'ten' to a number or dividing by zero. A try block surrounds an operation that may fail; a matching except block handles the specific error.

Catch only the exceptions you expect. In this lesson, ValueError means numeric text could not be converted and ZeroDivisionError means the denominator was zero. Broad except clauses can hide errors you did not intend to handle.

The practice function accepts two strings because real input often begins as text. It converts them with float, divides, and returns a numeric result for valid input. For the two expected failures it returns a clear message instead of crashing.

  • try runs code that may raise an exception
  • except ValueError handles invalid numeric text
  • except ZeroDivisionError handles a zero denominator

See it step by step

Read the code, predict the output, then compare it with the result.

01. Catch bad numeric text

PYTHON
try:
    number = int("ten")
except ValueError:
    print("Enter digits")
EXPECTED OUTPUT
Enter digits

int raises ValueError for this text, and the matching handler prints a useful prompt.

02. Catch division by zero

PYTHON
try:
    print(8 / 0)
except ZeroDivisionError:
    print("Choose a nonzero divisor")
EXPECTED OUTPUT
Choose a nonzero divisor

The handler catches only division by zero, so the program can continue.

03. Successful try skips except

PYTHON
try:
    print(float("6") / 2)
except ValueError:
    print("Invalid")
EXPECTED OUTPUT
3.0

No ValueError occurs, so Python skips the except body.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Put only the risky operation in try

A try block should be narrow enough that a matching error has one clear meaning. If a large block contains several conversions and calculations, an except ValueError may catch a failure from a different line than the one you expected. Do the conversion inside try, put work that needs a valid value in else, and put any unconditional final message in finally.

The else block runs only when try finishes without an exception. The finally block runs whether conversion succeeds or fails; it is commonly used to release resources, though with open is usually clearer for files. This example prints the sequence so you can see the control flow. Change the text to '12' and predict which message disappears.

PYTHON
text = "not-a-number"
try:
    number = int(text)
except ValueError:
    print("Conversion failed")
else:
    print(number * 2)
finally:
    print("Attempt finished")
EXPECTED OUTPUT
Conversion failed
Attempt finished
Follow the reasoning
  1. int raises ValueError, so the matching except block runs.
  2. else is skipped, but finally still prints its message.
  3. With text set to '12', else prints 24 before finally.
02 / 03

Handle missing configuration deliberately

Not every failure is a bad number. Looking up a missing dictionary key raises KeyError. Sometimes a missing value is normal and you should use dict.get with a default; other times it means the configuration is incomplete and you should tell the caller. Decide which case applies instead of catching every exception and returning a vague message.

In this example, host is required, while port has a reasonable default. A specific KeyError handler gives a useful result for missing host. The example only formats port as text; a real network client would also need to validate its type and range before connecting. Try removing port from the first dictionary and predict the address.

PYTHON
def address(settings):
    try:
        host = settings["host"]
    except KeyError:
        return "Missing host"
    port = settings.get("port", 8000)
    return f"{host}:{port}"

print(address({"host": "localhost", "port": 9000}))
print(address({"port": 9000}))
EXPECTED OUTPUT
localhost:9000
Missing host
Follow the reasoning
  1. Square-bracket lookup requires host and raises KeyError if absent.
  2. get supplies 8000 only when optional port is absent.
  3. Remove port from the first call to observe the default.
03 / 03

Raise an error where the rule is known

Exceptions are not only accidents Python discovers. Your own function can raise one when it detects invalid input. The function knows the business rule; the caller knows how to respond. This separation keeps a reusable function from printing a warning or silently returning a fabricated value that later calculations might treat as real.

Choose an exception type and message that match the problem. ValueError fits a valid kind of argument with an unacceptable value. Catch it at the boundary where a person can correct the input. During development, avoid catching an error so broadly that unrelated bugs are disguised as ordinary validation failures. Try changing age to zero to follow the successful path.

PYTHON
def ticket_price(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return 5 if age < 18 else 10

for age in [12, -1, 20]:
    try:
        print(ticket_price(age))
    except ValueError as error:
        print(error)
EXPECTED OUTPUT
5
Age cannot be negative
10
Follow the reasoning
  1. The function rejects only the impossible negative age.
  2. The caller catches that specific error and continues to the next input.
  3. Set the middle age to zero and predict all three prices.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete safe_divide(top_text, bottom_text). Return a float for valid numbers, Invalid number for ValueError, and Cannot divide by zero for ZeroDivisionError. The prepared calls should print 5.0, Invalid number, and Cannot divide by zero on separate lines.
Need a hint?

Inside try, convert with float and return top / bottom. Follow with two specific except clauses: ValueError and ZeroDivisionError. Each clause returns its matching message.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.What exception does int('ten') raise?

    02.What happens to a matching except block when try succeeds?

    03.Why prefer except ValueError over a bare except here?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Catching the wrong exception

    COMMON MISTAKE
    try:
        print(4 / 0)
    except ValueError:
        print('Bad number')
    FIX
    try:
        print(4 / 0)
    except ZeroDivisionError:
        print('Cannot divide by zero')

    What happens: ZeroDivisionError still stops the program.
    The except type must match the error that may be raised.

    Using a bare except

    COMMON MISTAKE
    try:
        risky_code()
    except:
        print('Something failed')
    FIX
    try:
        number = float('bad')
    except ValueError:
        print('Invalid number')

    What happens: Unrelated bugs are hidden behind a vague message.
    Catch an expected error by name so other failures remain visible.

    Sources and further reading