Exception Handling#

When an error occurs at runtime, Python raises an exception. If the exception isn’t handled, the program terminates and displays an error message:

1
2
3
4
5
6
7
8
9
print(10 / 0)
# ZeroDivisionError: division by zero

int("hello")
# ValueError: invalid literal for int() with base 10: 'hello'

numbers = [1, 2, 3]
print(numbers[10])
# IndexError: list index out of range

try / except basic usage#

Wrap potentially failing code in try, and handle exceptions in except:

1
2
3
4
5
6
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Error: cannot divide by zero")

print("Program continues executing")

Catching multiple exception types#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("Divisor cannot be zero")
    except TypeError:
        print("Arguments must be numbers")
    return None

safe_divide(10, 0)       # Divisor cannot be zero
safe_divide(10, "two")   # Arguments must be numbers

Single except with a tuple of exception types#

1
2
3
4
try:
    value = int(input("Enter a number: "))
except (ValueError, TypeError):
    print("Invalid input format")

Catching all exceptions#

1
2
3
4
5
6
try:
    # Some uncertain operation
    risky_operation()
except Exception as e:
    print(f"An error occurred: {e}")
    print(f"Exception type: {type(e).__name__}")

else and finally#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
try:
    result = 10 / 2
except ZeroDivisionError:
    print("Division by zero")
else:
    # Runs when no exception occurred in the try block
    print(f"Calculation successful: {result}")
finally:
    # Always runs, whether or not an exception occurred
    print("Calculation complete")

Typical use of finally: ensure resources are released (e.g., closing files or database connections):

1
2
3
4
5
6
7
8
9
file = None
try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("File not found")
finally:
    if file:
        file.close()  # Always close, no matter what

Common exception types#

ExceptionWhen it occurs
ValueErrorValue has wrong format (e.g., int("abc"))
TypeErrorWrong type (e.g., "2" + 2)
ZeroDivisionErrorDivision by zero
IndexErrorList index out of range
KeyErrorKey not found in dictionary
AttributeErrorObject has no such attribute or method
FileNotFoundErrorSpecified file not found
NameErrorUsing an undefined variable
ImportErrorUnable to import a module
StopIterationIterator has no more elements

Getting exception info#

1
2
3
4
5
try:
    result = int("abc")
except ValueError as e:
    print(f"Exception message: {e}")
    print(f"Exception type: {type(e).__name__}")

Raising exceptions manually with raise#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def set_age(age):
    if not isinstance(age, int):
        raise TypeError("Age must be an integer")
    if age < 0 or age > 150:
        raise ValueError(f"Age {age} is out of reasonable range (0-150)")
    print(f"Age set to {age}")

try:
    set_age(-5)
except ValueError as e:
    print(f"Error: {e}")

try:
    set_age("twenty")
except TypeError as e:
    print(f"Error: {e}")

Custom exception classes#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class InsufficientFundsError(Exception):
    """Raised when account balance is insufficient"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"Insufficient funds: have {balance}, need {amount}")


class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        print(f"Withdrew {amount}, remaining balance: {self.balance}")


account = BankAccount(1000)

try:
    account.withdraw(500)   # Withdrew 500, remaining balance: 500
    account.withdraw(800)   # Exceeds balance
except InsufficientFundsError as e:
    print(f"Transaction failed: {e}")

Practical examples#

Safe type conversion#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def safe_int(value, default=0):
    """Try to convert a value to int; return default on failure"""
    try:
        return int(value)
    except (ValueError, TypeError):
        return default

print(safe_int("42"))      # 42
print(safe_int("abc"))     # 0
print(safe_int(None))      # 0
print(safe_int("3.7"))     # 0 (int() does not accept decimal strings)

Retry mechanism#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import random

def fetch_data():
    """Simulate an operation that may fail"""
    if random.random() < 0.7:  # 70% chance of failure
        raise ConnectionError("Connection failed")
    return "Data retrieved successfully"

max_retries = 3
for attempt in range(1, max_retries + 1):
    try:
        result = fetch_data()
        print(result)
        break
    except ConnectionError as e:
        print(f"Attempt {attempt} failed: {e}")
        if attempt == max_retries:
            print("Maximum retries reached, giving up")

Input validation loop#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def get_positive_number(prompt):
    """Keep prompting the user until a valid positive integer is entered"""
    while True:
        try:
            value = int(input(prompt))
            if value <= 0:
                raise ValueError("Must be a positive integer")
            return value
        except ValueError as e:
            print(f"Input error: {e}. Please try again.")

# n = get_positive_number("Enter a positive integer: ")
# print(f"You entered: {n}")