Free Ebook cover Python for Absolute Beginners: Variables, Loops, and Small Useful Scripts

Python for Absolute Beginners: Variables, Loops, and Small Useful Scripts

New course

14 pages

Handling Errors with Try/Except and Defensive Checks

Capítulo 9

Estimated reading time: 11 minutes

+ Exercise

Why Errors Happen (and Why You Should Plan for Them)

When you run a Python program, you are asking it to follow instructions exactly. But real programs interact with messy reality: users type unexpected input, network connections drop, files might be missing, and data may not be in the format you assumed. When something goes wrong, Python raises an exception. If you do nothing, the program stops and prints an error message (a traceback). That is useful while developing, but it is usually not the experience you want for a small useful script.

Handling errors is about two complementary habits:

  • Try/except: catch exceptions that may happen and respond in a controlled way.
  • Defensive checks: prevent errors by verifying assumptions before you do something risky.

Good beginner scripts often use both: defensive checks for predictable problems, and try/except for unpredictable or external problems.

Understanding Exceptions in Plain Terms

An exception is an object that represents an error condition. Python raises it when it cannot continue normally. Common examples you will see in beginner scripts include:

  • ValueError: a value is the right type but has an invalid content (for example, converting "abc" to an integer).
  • TypeError: you used the wrong type (for example, adding a number to a string).
  • KeyError: a dictionary key was not found.
  • IndexError: a list index was out of range.
  • ZeroDivisionError: division by zero.

When an exception is raised and not handled, Python stops the program at that point. Handling exceptions lets your program choose what to do instead: ask again, use a default value, skip a bad record, or show a friendly message.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

The Basic Try/Except Pattern

The simplest structure is:

try:    # code that might fail    risky_operation()except SomeError:    # what to do if that specific error happens    handle_it()

Python runs the code inside try. If it raises SomeError, Python jumps to the except block. If no error happens, the except block is skipped.

Example: Converting User Input to an Integer

A classic error is converting text to a number. If the user types something that is not a valid integer, int() raises ValueError.

raw = input("Enter your age: ")try:    age = int(raw)except ValueError:    print("Please enter a whole number.")    age = 0print("Age stored as:", age)

This keeps the program from crashing. However, setting age = 0 might not be the best behavior. Often you want to ask again until you get a valid value.

Practical Step-by-Step: A Safe Number Prompt

One of the most useful patterns for beginner scripts is a “safe prompt” that keeps asking until it gets valid input. Here is a step-by-step approach.

Step 1: Identify the risky operation

The risky operation is converting input text to a number:

number = int(input("Enter a number: "))

This can raise ValueError.

Step 2: Wrap it in try/except

try:    number = int(input("Enter a number: "))except ValueError:    print("That was not a valid integer.")

Now it won’t crash, but it still doesn’t produce a number if invalid input happens.

Step 3: Repeat until valid

while True:    raw = input("Enter a number: ")    try:        number = int(raw)        break    except ValueError:        print("That was not a valid integer. Try again.")print("You entered:", number)

This pattern is robust and friendly. The loop continues only when conversion succeeds.

Step 4: Add extra defensive checks (range, rules)

Sometimes a value can be a valid integer but still not acceptable (for example, a menu choice must be 1–5). That is not a conversion error; it is a rule check. Use an if check after conversion.

while True:    raw = input("Choose an option (1-5): ")    try:        choice = int(raw)    except ValueError:        print("Please enter a whole number.")        continue    if 1 <= choice <= 5:        break    print("Choice must be between 1 and 5.")print("You chose:", choice)

Notice how try/except handles the conversion problem, and the defensive check handles the business rule.

Catching Specific Exceptions (Not Everything)

It is tempting to write:

try:    do_something()except Exception:    print("Something went wrong")

This catches almost all errors, including ones you did not expect. That can hide bugs and make debugging harder. A better beginner habit is:

  • Catch the specific exceptions you expect.
  • Keep the try block as small as possible (only the lines that might raise the exception you are handling).

Example: Dictionary Lookup with a Fallback

If you access a missing key with dict[key], Python raises KeyError. You can handle it, but often a defensive approach is cleaner.

Try/except approach:

prices = {"apple": 0.50, "banana": 0.30}item = input("Item: ")try:    price = prices[item]except KeyError:    print("Unknown item")    price = Noneprint("Price:", price)

Defensive approach using a safe lookup:

prices = {"apple": 0.50, "banana": 0.30}item = input("Item: ")price = prices.get(item)if price is None:    print("Unknown item")else:    print("Price:", price)

In many cases, get() is preferable because it avoids exceptions entirely for a common situation.

Else and Finally: Two Helpful Add-ons

Using else with try/except

The else block runs only if no exception happened in the try block. This can make your code clearer by separating the “success path” from the “error path.”

raw = input("Enter a percentage (0-100): ")try:    pct = int(raw)except ValueError:    print("Not a whole number")else:    if 0 <= pct <= 100:        print("Stored:", pct)    else:        print("Out of range")

Here, conversion errors are handled in except, and the range check happens only when conversion succeeded.

Using finally for cleanup

The finally block runs no matter what: whether an exception happened or not. It is mainly used for cleanup actions that must happen even if something fails (for example, releasing a resource, stopping a timer, resetting a state variable).

print("Starting task")try:    x = 10 / 0except ZeroDivisionError:    print("Math error")finally:    print("Task finished (cleanup done)")

Even though division fails, the final message still prints.

Defensive Checks: Preventing Errors Before They Happen

Defensive programming means you assume inputs and data can be wrong, and you check conditions before performing operations that could fail. This is not about being pessimistic; it is about building scripts that behave predictably.

Common Defensive Checks You Can Use

  • Check for empty input: user pressed Enter without typing.
  • Check numeric rules: range limits, non-negative, not zero.
  • Check membership: is a key in a dictionary, is an item in a list of allowed values.
  • Check string format: basic patterns like “contains @” for a simple email check (not perfect, but useful).
  • Check length: password length, code length, etc.

Example: Avoiding ZeroDivisionError with a Check

raw = input("Enter a divisor: ")try:    divisor = float(raw)except ValueError:    print("Not a number")else:    if divisor == 0:        print("Divisor cannot be zero")    else:        result = 100 / divisor        print("Result:", result)

The defensive check (divisor == 0) prevents a predictable error.

Choosing Between Try/Except and Defensive Checks

A useful rule of thumb:

  • Use defensive checks when you can easily test the condition before doing the operation (for example, check for zero, check membership, check length).
  • Use try/except when the failure depends on something external or hard to predict (for example, parsing, conversions, operations that can fail for many reasons).

Also, some operations are naturally “ask forgiveness” in Python: you try the operation and handle failure. Others are naturally “look before you leap”: you check first. Both are acceptable; clarity matters most.

Handling Multiple Exceptions

Sometimes different errors can happen in the same area, and you want different messages or actions.

Separate except blocks

raw = input("Enter a whole number: ")try:    n = int(raw)    value = 100 / nexcept ValueError:    print("That was not a whole number")except ZeroDivisionError:    print("Number cannot be zero")else:    print("Result:", value)

This handles two different problems cleanly.

One except for several exception types

If you want the same handling for multiple exceptions, you can group them:

raw = input("Enter a whole number: ")try:    n = int(raw)    value = 100 / nexcept (ValueError, ZeroDivisionError):    print("Please enter a non-zero whole number")else:    print("Result:", value)

Capturing the Exception Message

Sometimes you want to show a more detailed message (especially while you are still testing your script). You can capture the exception object with as.

raw = input("Enter an integer: ")try:    n = int(raw)except ValueError as e:    print("Conversion failed:", e)

Be careful about showing raw error messages to end users in a polished script, but it is very helpful while learning and debugging.

Raising Your Own Errors for Invalid States

Not all errors come from Python automatically. Sometimes your program reaches a situation that should never happen if the data is correct. In those cases, you can raise an exception yourself. This is useful when you want to stop the current operation and clearly signal that something is wrong.

def calculate_discount(price, pct):    if price < 0:        raise ValueError("price cannot be negative")    if not (0 <= pct <= 100):        raise ValueError("pct must be between 0 and 100")    return price * (pct / 100)

Here, defensive checks are used, but instead of printing, the function raises a clear error. The caller can decide how to handle it.

Handling a raised error

raw_price = input("Price: ")raw_pct = input("Discount %: ")try:    price = float(raw_price)    pct = float(raw_pct)    amount = calculate_discount(price, pct)except ValueError as e:    print("Cannot calculate discount:", e)else:    print("Discount amount:", amount)

This separates responsibilities: the function enforces rules, and the caller decides the user-facing behavior.

Defensive Checks for Data Structures

Many beginner scripts manipulate lists and dictionaries. Errors often happen when you assume something exists when it does not.

Example: Safe access to a list element

Accessing an index that does not exist raises IndexError. If the index comes from user input or variable data, you can defend against it.

items = ["red", "green", "blue"]raw = input("Pick an index (0-2): ")try:    idx = int(raw)except ValueError:    print("Not an integer")else:    if 0 <= idx < len(items):        print("You picked:", items[idx])    else:        print("Index out of range")

Here, the defensive check uses len(items) so it stays correct even if the list changes size.

Example: Avoiding KeyError with membership checks

scores = {"Ana": 10, "Ben": 7}name = input("Name: ")if name in scores:    print("Score:", scores[name])else:    print("No score for that name")

This is a simple defensive check that avoids exceptions entirely.

Designing Friendly Error Messages

Error handling is not only technical; it is also communication. A good message tells the user:

  • What went wrong (in simple terms)
  • What input is expected
  • What they can do next (try again, choose from options)

Compare these two messages:

  • Bad: ValueError
  • Better: Please enter a whole number like 1, 2, or 3.

When you catch an exception, you can provide context without exposing internal details.

Keeping Try Blocks Small and Focused

A common beginner mistake is wrapping too much code in a single try/except. That can make it unclear which line caused the error and can accidentally hide unrelated bugs.

Less clear:

try:    raw = input("Number: ")    n = int(raw)    result = 100 / n    print("Result:", result)except ValueError:    print("Not a number")except ZeroDivisionError:    print("Cannot be zero")

Clearer and more controlled:

raw = input("Number: ")try:    n = int(raw)except ValueError:    print("Not a whole number")else:    if n == 0:        print("Cannot be zero")    else:        result = 100 / n        print("Result:", result)

In the second version, the conversion is the only thing inside the try block, and the rest is handled with normal logic.

When to Let Errors Crash the Program

Not every error should be caught. If an error indicates a programming mistake (for example, using the wrong variable name, passing the wrong type inside your own code), it is often better to let it crash during development so you can fix it. Try/except is most valuable for:

  • Unreliable input (user typing, data from outside)
  • Operations that can fail due to environment (permissions, missing resources, unavailable services)
  • Parsing and conversions

As you build scripts, aim for a balance: catch expected problems and handle them gracefully, but do not hide unexpected bugs.

Mini-Recipe: Validating a Simple Comma-Separated Input

Many small scripts ask for a list of values in one line, like "3, 10, 25". Problems include extra spaces, empty items, or non-numbers. You can combine defensive checks and try/except.

raw = input("Enter comma-separated integers (e.g., 3,10,25): ")parts = [p.strip() for p in raw.split(",")]if any(p == "" for p in parts):    print("Please do not leave empty values between commas.")else:    numbers = []    bad = False    for p in parts:        try:            numbers.append(int(p))        except ValueError:            print("Not an integer:", p)            bad = True            break    if not bad:        print("Numbers:", numbers)        print("Count:", len(numbers))        print("Max:", max(numbers))

What this demonstrates:

  • Defensive check for empty segments (like "1,,2").
  • Try/except for conversion of each part.
  • Stop early when a bad value is found, instead of continuing with partial results.

Now answer the exercise about the content:

In a loop that asks the user to choose an option from 1 to 5, which approach correctly handles both non-numeric input and an out-of-range number?

You are right! Congratulations, now go to the next page

You missed! Try again.

Correct: try/except should catch conversion failures like ValueError, while an if defensive check should enforce the rule that the choice must be between 1 and 5.

Next chapter

Building a Personal Budget Tracker Script

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.