Progress0%

7 of 20 topics completed

Python Error Handling and Exceptions

Even the most carefully written programs can encounter unexpected situations. Python's exception handling system helps you deal with errors gracefully, making your programs more robust and user-friendly. In this tutorial, you'll learn how to catch and handle errors effectively.

💡 Why Error Handling Matters

Imagine you've created a calculator app that divides two numbers. What happens if a user tries to divide by zero? Without proper error handling, your program would crash! With error handling, you can display a helpful message and allow the user to try again.

Understanding Errors and Exceptions

In Python, there are two main types of errors:

Syntax Errors

These occur when Python can't understand your code because it violates the language rules.

Python
# Syntax error example
if x == 5 # Missing colon
print("x is 5")

Python will point out syntax errors before your program runs.

Exceptions

These occur during execution when something unexpected happens, even if the syntax is correct.

Python
# Exception example
x = 10 / 0 # ZeroDivisionError
print(y) # NameError - y is not defined

Exceptions happen while your program is running.

Common Python Exceptions

Python has many built-in exception types. Here are some you'll encounter frequently:

ExceptionDescriptionExample
ZeroDivisionErrorDividing by zero10 / 0
TypeErrorOperation on an inappropriate type"2" + 2
ValueErrorOperation on a value with the right type but inappropriate valueint("abc")
NameErrorUsing a variable that doesn't existprint(undefined_var)
IndexErrorAccessing an index that's out of rangemy_list = [1, 2]; my_list[5]
KeyErrorAccessing a dictionary key that doesn't existmy_dict = ; my_dict["key"]
FileNotFoundErrorTrying to open a file that doesn't existopen("missing.txt")

Basic Exception Handling with try/except

The try/except block is the foundation of error handling in Python:

Python
1# Basic structure
2try:
3 # Code that might cause an exception
4 result = 10 / 0
5except:
6 # Code that runs if an exception occurs
7 print("An error occurred!")

Let's look at a more practical example:

Python
1def divide_numbers(a, b):
2 try:
3 result = a / b
4 return result
5 except:
6 print("Error: Division by zero is not allowed!")
7 return None
8
9# Test the function
10print(divide_numbers(10, 2)) # Output: 5.0
11print(divide_numbers(10, 0)) # Output: Error: Division by zero is not allowed!
12 # None

⚠️ Best Practice

Avoid using a bare except: clause as it will catch all exceptions, including ones you might not expect. This can make debugging harder. Instead, specify which exceptions you want to catch.

Catching Specific Exceptions

It's better to catch specific exceptions rather than using a blanket except clause:

Python
1def divide_numbers_better(a, b):
2 try:
3 result = a / b
4 return result
5 except ZeroDivisionError:
6 print("Error: Division by zero is not allowed!")
7 return None
8 except TypeError:
9 print("Error: Please provide numbers!")
10 return None
11
12# Test with different inputs
13print(divide_numbers_better(10, 2)) # Output: 5.0
14print(divide_numbers_better(10, 0)) # Output: Error: Division by zero is not allowed!
15print(divide_numbers_better(10, "2")) # Output: Error: Please provide numbers!

Handling Multiple Exceptions

You can handle multiple exceptions in different ways:

Multiple except Blocks

Python
try:
# Code that might raise exceptions
num = int(input("Enter a number: "))
result = 100 / num
print(f"100 divided by {num} is {result}")
except ValueError:
print("That's not a valid number!")
except ZeroDivisionError:
print("You can't divide by zero!")

Grouping Exceptions

Python
try:
# Code that might raise exceptions
num = int(input("Enter a number: "))
result = 100 / num
print(f"100 divided by {num} is {result}")
except (ValueError, ZeroDivisionError):
print("Please enter a non-zero number!")

Getting Exception Information

You can capture the actual exception object to get more information about what went wrong:

Python
1try:
2 # Try to open a file that doesn't exist
3 with open("nonexistent_file.txt", "r") as file:
4 content = file.read()
5except FileNotFoundError as error:
6 print(f"Error: {error}")
7 # Output: Error: [Errno 2] No such file or directory: 'nonexistent_file.txt'
8
9 # You can access attributes of the error object
10 print(f"Error code: {error.errno}")
11 print(f"Error message: {error.strerror}")
12 print(f"Filename: {error.filename}")

The else and finally Clauses

You can extend try/except with else and finally clauses:

Try-Except Flow Diagram
Python
1try:
2 # This block might raise an exception
3 number = int(input("Enter a positive number: "))
4 if number <= 0:
5 raise ValueError("That's not a positive number!")
6except ValueError as e:
7 # This block runs if an exception occurs
8 print(f"Error: {e}")
9else:
10 # This block runs only if NO exception occurs
11 print(f"You entered {number}, which is positive!")
12finally:
13 # This block ALWAYS runs, regardless of whether an exception occurred
14 print("Thank you for using our program!")

try

Contains code that might cause an exception.

except

Runs if an exception occurs in the try block.

else

Runs if the try block completes with no exceptions.

finally

Always runs, regardless of whether an exception occurred. Useful for cleanup actions.

Raising Exceptions

Sometimes you want to trigger exceptions yourself when certain conditions aren't met:

Python
1def set_age(age):
2 if not isinstance(age, int):
3 raise TypeError("Age must be an integer")
4
5 if age < 0 or age > 150:
6 raise ValueError("Age must be between 0 and 150")
7
8 print(f"Age set to {age}")
9
10# Test the function
11try:
12 set_age(25) # Works fine
13 set_age(-5) # Raises ValueError
14except ValueError as e:
15 print(f"Error: {e}")
16
17try:
18 set_age("twenty") # Raises TypeError
19except TypeError as e:
20 print(f"Error: {e}")

Creating Custom Exceptions

You can create your own exception types by inheriting from existing exceptions:

Python
1class InvalidAgeError(Exception):
2 """Exception raised for invalid age values."""
3 pass
4
5class InvalidEmailError(Exception):
6 """Exception raised for invalid email format."""
7 def __init__(self, email, message="Invalid email format"):
8 self.email = email
9 self.message = message
10 super().__init__(self.message)
11
12 def __str__(self):
13 return f"{self.message}: {self.email}"
14
15# Using custom exceptions
16def register_user(name, age, email):
17 if age < 13:
18 raise InvalidAgeError(f"User must be at least 13 years old. Got: {age}")
19
20 if "@" not in email or "." not in email:
21 raise InvalidEmailError(email)
22
23 print(f"User {name} registered successfully!")
24
25# Test with different inputs
26try:
27 register_user("Alice", 25, "alice@example.com") # Valid input
28 register_user("Bob", 10, "bob@example.com") # Invalid age
29except InvalidAgeError as e:
30 print(f"Age error: {e}")
31except InvalidEmailError as e:
32 print(f"Email error: {e}")

Practical Error Handling: User Input Validation

Let's see a practical example of error handling for user input:

Python
1def get_integer_input(prompt, min_value=None, max_value=None):
2 """
3 Get an integer input from the user with validation.
4
5 Args:
6 prompt: The message to display to the user
7 min_value: Optional minimum allowed value
8 max_value: Optional maximum allowed value
9
10 Returns:
11 An integer within the specified range
12 """
13 while True:
14 try:
15 value = int(input(prompt))
16
17 if min_value is not None and value < min_value:
18 print(f"Error: Value must be at least {min_value}")
19 continue
20
21 if max_value is not None and value > max_value:
22 print(f"Error: Value must be at most {max_value}")
23 continue
24
25 return value # Valid input, exit the loop
26
27 except ValueError:
28 print("Error: Please enter a valid integer")
29
30# Using the function
31age = get_integer_input("Enter your age (18-120): ", 18, 120)
32print(f"You entered: {age}")
33
34# Example interaction:
35# Enter your age (18-120): abc
36# Error: Please enter a valid integer
37# Enter your age (18-120): 15
38# Error: Value must be at least 18
39# Enter your age (18-120): 130
40# Error: Value must be at most 120
41# Enter your age (18-120): 25
42# You entered: 25

Advanced Pattern: Context Managers

The with statement (used with files) is based on Python's context manager protocol, which provides automatic resource management and error handling:

Python
1# File handling with context manager
2with open("example.txt", "w") as file:
3 file.write("Hello, world!")
4 # File is automatically closed when the block ends, even if an exception occurs
5
6# Creating a custom context manager
7class DatabaseConnection:
8 def __init__(self, connection_string):
9 self.connection_string = connection_string
10 self.connection = None
11
12 def __enter__(self):
13 print(f"Connecting to database: {self.connection_string}")
14 # In a real app, this would be: self.connection = db.connect(self.connection_string)
15 self.connection = "DB CONNECTION"
16 return self.connection
17
18 def __exit__(self, exc_type, exc_val, exc_tb):
19 print("Closing database connection")
20 # In a real app: self.connection.close()
21
22 # If we return True, any exception will be suppressed
23 if exc_type is not None:
24 print(f"An error occurred: {exc_val}")
25 return True # Suppress the exception
26
27# Using our custom context manager
28try:
29 with DatabaseConnection("postgresql://localhost/mydb") as conn:
30 print("Connected!")
31 # Do database operations...
32
33 # Let's cause an error
34 if True:
35 raise ValueError("Something went wrong!")
36
37 print("This line won't execute")
38except ValueError:
39 print("This exception was suppressed by the context manager")
40
41print("Program continues...")

🎯 Try it yourself!

Create a function that reads a JSON file and safely handles these potential errors:

  • File not found
  • File contains invalid JSON
  • Missing expected fields in the JSON data

Best Practices for Error Handling

  1. Be specific - Catch only the exceptions you expect and can handle.
  2. Fail early, fail loudly - Detect and report errors as soon as possible.
  3. Don't catch what you can't handle - Let exceptions you can't properly handle propagate up.
  4. Keep the try block small - Only wrap code that might cause the exception.
  5. Use finally for cleanup - Ensure resources are properly closed or released.
  6. Provide helpful error messages - Users should understand what went wrong.
  7. Log exceptions - In larger applications, log exceptions for debugging.

Summary

In this tutorial, you've learned:

  • How to use try/except to handle exceptions
  • Catching specific exception types
  • Using else and finally clauses
  • Raising exceptions when conditions aren't met
  • Creating custom exception classes
  • Best practices for effective error handling

Effective error handling makes your programs more reliable and user-friendly. Rather than crashing when something unexpected happens, your program can respond gracefully and continue running or exit cleanly.

Related Tutorials

Learn how to work with files in Python.

Learn more

Master creating and using functions in Python.

Learn more

Learn about if statements, loops, and control flow in Python.

Learn more