0%

44- Error Handling with try & except

Handle errors gracefully. Prevent crashes. Make your programs robust. Master Python’s exception handling system.

Your program is running. Suddenly, something goes wrong. The user enters text when you expected a number. A file does not exist. The network connection fails. A division by zero occurs. In many programming languages, these errors would crash your program. But Python has a better way: exceptions and exception handling. When an error occurs, Python raises an exception. If you do nothing, the program stops and shows an error message. But you can catch exceptions using try and except blocks. You can handle the error gracefully, retry the operation, or clean up resources. Exception handling is not just about preventing crashes. It is about writing robust programs that can recover from unexpected situations. It is about giving users helpful error messages instead of cryptic tracebacks. It is about professional-grade software.

🕯️ Magic Note

Exceptions are not errors. They are events. Python raises an exception when something exceptional happens—something that does not fit the normal flow of the program. Exceptions can be caught and handled. This is the “Easier to ask for forgiveness than permission” (EAFP) style, which is central to Python’s design philosophy.

Basic try-except Syntax
The simplest form: put risky code in the try block. If an exception occurs, Python jumps to the except block.

Python

try:

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

print(f”You entered {number}”)

except ValueError:

print(“That was not a valid number!”)

# If user enters “hello”, the program does not crash.

💡 Always catch specific exceptions. Catching all exceptions with bare except: is dangerous because it hides errors you did not anticipate.
Catching Specific Exceptions
Different errors raise different exceptions. Catch them specifically to handle each appropriately.

Python

try:

num = int(input(“Enter a number: “))

result = 100 / num

print(f”100 / {num} = {result}”)

except ValueError:

print(“Error: Please enter a valid number”)

except ZeroDivisionError:

print(“Error: Cannot divide by zero”)

🕯️ Magic Note

When multiple except blocks are present, Python checks them in order. The first matching exception block runs. This is why you should put more specific exceptions before more general ones.

Catching Multiple Exceptions in One Block
You can catch multiple exception types in a single except block using a tuple.

Python

try:

value = int(input(“Enter a number: “))

result = 100 / value

print(f”Result: {result}”)

except (ValueError, ZeroDivisionError) as e:

print(f”Error: {e}”)

Accessing the Exception Object
Use as to capture the exception object and access its details.

Python

try:

with open(“nonexistent.txt”, “r”) as f:

content = f.read()

except FileNotFoundError as e:

print(f”Error type: {type(e).__name__}”)

print(f”Error message: {e}”)

print(f”Errno: {e.errno}”)

print(f”Filename: {e.filename}”)

Catching All Exceptions (Use with Caution)
Bare except: catches all exceptions, including KeyboardInterrupt and SystemExit. Usually better to catch Exception.

Python

# Dangerous: catches everything

try:

risky_operation()

except: # Bare except – not recommended

print(“Something went wrong”)

# Better: catches all normal exceptions

try:

risky_operation()

except Exception as e:

print(f”Something went wrong: {e}”)

⚠️ Avoid bare except:. It catches KeyboardInterrupt (Ctrl+C) and SystemExit, making it impossible to terminate your program gracefully. Use except Exception instead if you need to catch everything.
The else Clause
The else block runs if no exception occurs. Put code that should only run on success.

Python

try:

file = open(“data.txt”, “r”)

except FileNotFoundError:

print(“File not found. Creating new file.”)

file = open(“data.txt”, “w”)

else:

# This runs only if no exception occurred

content = file.read()

print(f”File contained: {content}”)

finally:

file.close()

🕯️ Magic Note

The else clause is useful for code that should not be in the try block (where it might catch exceptions you did not intend) but should only run if the try succeeded.

The finally Clause
The finally block always runs, whether an exception occurred or not. It is used for cleanup (closing files, releasing resources).

Python

file = None

try:

file = open(“important.txt”, “r”)

content = file.read()

print(content)

except FileNotFoundError:

print(“File not found”)

except PermissionError:

print(“Permission denied”)

finally:

# This ALWAYS runs

if file:

file.close()

print(“File closed”)

Complete try-except-else-finally
All four clauses can be combined. The order matters.

Python

def divide_numbers(a, b):

try:

result = a / b

except ZeroDivisionError:

print(“Error: Division by zero”)

return None

except TypeError:

print(“Error: Both arguments must be numbers”)

return None

else:

print(“Division successful”)

return result

finally:

print(“Operation completed”)

print(divide_numbers(10, 2))

print(divide_numbers(10, 0))

print(divide_numbers(10, “2”))

Raising Exceptions (raise)
You can raise exceptions intentionally using the raise keyword. Useful for validation and signaling errors.

Python

def set_age(age):

if age < 0:

raise ValueError(“Age cannot be negative”)

if age > 150:

raise ValueError(“Age is unreasonably high”)

self.age = age

# Reraising an exception

try:

set_age(-5)

except ValueError as e:

print(f”Caught: {e}”)

raise # Reraises the same exception

🕯️ Magic Note

You can create your own exception classes by inheriting from Exception. This is useful for libraries where you want users to catch specific errors from your code.

Python

# Creating custom exceptions

class InsufficientFundsError(Exception):

pass

class AccountNotFoundError(Exception):

def __init__(self, account_id):

self.account_id = account_id

super().__init__(f”Account {account_id} not found”)

def withdraw(account, amount):

if account.balance < amount:

raise InsufficientFundsError(f”Need {amount – account.balance} more”)

account.balance -= amount

Common Built-in Exceptions
Python has many built-in exceptions. Here are the ones you will see most often.
ExceptionWhen It Occurs
ValueErrorFunction receives argument of correct type but inappropriate value
TypeErrorOperation applied to object of wrong type
IndexErrorSequence index out of range
KeyErrorDictionary key not found
FileNotFoundErrorFile or directory does not exist
ZeroDivisionErrorDivision or modulo by zero
ImportErrorImport statement fails to find module
AttributeErrorObject does not have requested attribute
NameErrorVariable name not found
KeyboardInterruptUser presses Ctrl+C
StopIterationNext() called on iterator with no items
Practical Example: Robust Input Function
Create a function that keeps asking until valid input is received.

Python

def get_integer(prompt, min_value=None, max_value=None):

“””Get an integer from the user with optional range validation.”””

while True:

try:

value = int(input(prompt))

if min_value is not None and value < min_value:

print(f”Value must be at least {min_value}”)

continue

if max_value is not None and value > max_value:

print(f”Value must be at most {max_value}”)

continue

return value

except ValueError:

print(“Invalid input. Please enter a valid integer.”)

# Usage

age = get_integer(“Enter your age: “, min_value=0, max_value=150)

score = get_integer(“Enter score (0-100): “, 0, 100)

Practical Example: Safe File Reader
Read a file safely with proper error handling.

Python

def safe_read_file(filename):

“””Read a file safely, returning content or None if error.”””

try:

with open(filename, “r”, encoding=”utf-8″) as f:

return f.read()

except FileNotFoundError:

print(f”Error: File ‘{filename}’ not found”)

return None

except PermissionError:

print(f”Error: Permission denied to read ‘{filename}'”)

return None

except UnicodeDecodeError as e:

print(f”Error: Cannot decode file ‘{filename}’: {e}”)

return None

except Exception as e:

print(f”Unexpected error reading ‘{filename}’: {e}”)

return None

content = safe_read_file(“config.txt”)

if content:

print(“File read successfully”)

The EAFP Pattern (Easier to Ask for Forgiveness than Permission)
Python encourages trying an operation and handling exceptions rather than checking conditions first.

Python

# LBYL (Look Before You Leap) – C-style

if “name” in user_data:

name = user_data[“name”]

else:

name = “Unknown”

# EAFP (Easier to Ask for Forgiveness than Permission) – Pythonic

try:

name = user_data[“name”]

except KeyError:

name = “Unknown”

# Even simpler with .get() for dictionaries

name = user_data.get(“name”, “Unknown”)

🕯️ Magic Note

EAFP is Pythonic because it avoids race conditions (the condition might change between check and action) and is often faster when the success case is common. However, use good judgment—sometimes LBYL is clearer.

Common Mistakes with Exception Handling
  • Using bare except: that catches too much (including Ctrl+C)
  • Empty except block that silently ignores errors
  • Catching exceptions too broadly (e.g., Exception when specific types are needed)
  • Not cleaning up resources in finally or using with statements
  • Raising generic Exception instead of specific subclasses
  • Putting too much code in the try block (masking unexpected errors)

Python

# Bad: Empty except

try:

result = 10 / 0

except:

pass # Silent failure – very dangerous!

# Better: Log or handle appropriately

try:

result = 10 / 0

except ZeroDivisionError as e:

print(f”Error: {e}”)

result = None

Check Your Understanding
  • What is the difference between except ValueError: and except:?
  • When does the else clause in a try-except block execute?
  • What is the purpose of the finally clause?
  • Write a function that divides two numbers and handles division by zero gracefully.
  • How do you create a custom exception?
  • What does EAFP stand for and why is it Pythonic?

⚡ Whisper

Errors are not enemies. They are messengers. The exception says: “Something unexpected happened. Pay attention.” You can ignore the message, and the program will crash. Or you can listen. You can wrap the risky code in try and catch the exception with except. You can clean up with finally. You can succeed silently with else. And when you detect a problem yourself, you can raise your own exception with raise. This is not defensive programming. This is resilient programming. Your code does not pretend that errors never happen. It prepares for them. It handles them. It recovers. A program that never handles errors is a house of cards. A program that handles errors gracefully is a fortress. Build fortresses. Handle your exceptions. Your users will thank you. And your future self will too.

Related posts