
Chapter Outline
Chapter 5: Error Handling in Python
Bugs are inevitable—but crashes don’t have to be. This chapter explores how to handle errors gracefully.
5.1 Python Exceptions
Python provides a rich set of built-in exceptions that help developers gracefully handle unexpected conditions.
Common Built-in Exceptions
| Exception | Trigger |
|---|---|
ValueError | Invalid value for a function or operation |
TypeError | Wrong data type used |
ZeroDivisionError | Division by zero |
FileNotFoundError | Missing file reference |
IndexError | Invalid list index |
KeyError | Missing key in dictionary |
Basic Try-Except Block
python# ValueErrortry:x = int("abc")except ValueError as e:print("ValueError:", e)# TypeErrortry:length = len(42)except TypeError as e:print("TypeError:", e)# ZeroDivisionErrortry:result = 10 / 0except ZeroDivisionError as e:print("ZeroDivisionError:", e)# FileNotFoundErrortry:with open("missing.txt", "r") as f:content = f.read()except FileNotFoundError as e:print("FileNotFoundError:", e)# IndexErrortry:nums = [1, 2, 3]print(nums[5])except IndexError as e:print("IndexError:", e)# KeyErrortry:person = {"name": "Alice"}print(person["age"])except KeyError as e:print("KeyError:", e)
5.2 Raising Custom Exceptions
In Python, you can create your own custom exceptions by defining a new class that inherits from Python's built-in Exception class (or any of its subclasses). This allows you to raise and catch errors that are specific to your application's domain logic.
We talk about subclasses in details in the next chapter.
Example: Custom Exception
pythonimport mathclass NegativeInputError(Exception):"""Raised when a negative number is provided where not allowed."""passdef calculate_square_root(x):if x < 0:raise NegativeInputError("Cannot compute square root of a negative number.")return math.sqrt(x)
NegativeInputErroris your custom exception.- It inherits from Python's built-in
Exception. - The
passkeyword here is a placeholder indicating no additional logic is being added yet. Thepassstatement in Python is a no-op — it does nothing when executed. - The
raisekeyword is used to raise an exception of a specific type, be they built-in or custom.
Output:
bashTraceback (most recent call last):File "/.../custom_exception.py", line 16, in <module>rt2 = calculate_square_root(-5)^^^^^^^^^^^^^^^^^^^^^^^^^File "/.../custom_exception.py", line 10, in calculate_square_rootraise NegativeInputError("Cannot compute square root of a negative number.")NegativeInputError: Cannot compute square root of a negative number.
A stack trace similar to the above is produced when the earlier code is executed.
Adding More to Your Custom Exception
You can also customize your exception further by overriding the __init__ or __str__ methods:
pythonclass ValidationError(Exception):def __init__(self, field, message):self.field = fieldself.message = messagesuper().__init__(f"{field}: {message}")raise ValidationError("username", "Must not be empty")
Output:
bashValidationError: username: Must not be empty
5.5 Refactoring the Calculator: Fault-Tolerant Version
Here’s a refactored CLI calculator with logging and error handling.
calculator.py1def add(a, b): return a + b2def subtract(a, b): return a - b3def multiply(a, b): return a * b45def divide(a, b):6 if b == 0:7 raise ValueError("Cannot divide by zero.")8 return a / b910def main():11 try:12 operation = input("Choose operation (add/subtract/multiply/divide): ").strip().lower()13 num1 = float(input("Enter first number: "))14 num2 = float(input("Enter second number: "))1516 if operation == "add":17 result = add(num1, num2)18 elif operation == "subtract":19 result = subtract(num1, num2)20 elif operation == "multiply":21 result = multiply(num1, num2)22 elif operation == "divide":23 result = divide(num1, num2)24 else:25 return2627 print(f"Result: {result}")2829 except ValueError as ve:30 print(f"Value error: {ve}")31 except Exception as e:32 print("An unexpected error occurred.")3334if __name__ == "__main__":35 main()
5.6 Unit Testing Expected Exceptions
We’ll now test for normal results and for expected failures using pytest.
test_calculator.py1import pytest2from calculator import add, subtract, multiply, divide34def test_add():5 assert add(1, 2) == 367def test_subtract():8 assert subtract(5, 2) == 3910def test_multiply():11 assert multiply(3, 3) == 91213def test_divide():14 assert divide(6, 2) == 31516def test_divide_by_zero():17 with pytest.raises(ValueError, match="Cannot divide by zero."):18 divide(10, 0)
Check your understanding
Test your knowledge of Error Handling in Python