Error and Exception Handling in Python

Introduction

Every programmer encounters errors at some point, no matter how careful they are. Errors are a natural part of coding, and learning how to handle them effectively is an essential skill in any programming language — especially in Python.

Python provides a powerful framework for dealing with unexpected situations gracefully through its error and exception handling system. Instead of letting your program crash when something goes wrong, you can detect, handle, and recover from these issues intelligently.

This post will guide you through everything you need to know about error and exception handling in Python, including the use of try, except, finally, and raise. By the end of this guide, you will understand how to write robust and fault-tolerant Python programs.

1. Understanding Errors in Python

Errors in Python occur when something goes wrong during the execution of a program. They can be divided into two main types:

  1. Syntax Errors
  2. Exceptions (Runtime Errors)

Let’s understand both in detail.


2. Syntax Errors

A syntax error occurs when the Python interpreter detects incorrect syntax in your code. These errors prevent the program from running at all.

Example

print("Hello World"

Here, the closing parenthesis is missing. When you run this program, Python throws a SyntaxError.

Output

SyntaxError: unexpected EOF while parsing

Syntax errors are usually easy to detect and fix because Python clearly tells you where the error occurred.


3. Exceptions (Runtime Errors)

An exception is an error that occurs while the program is running. Unlike syntax errors, the program starts running, but crashes when it encounters an unexpected condition.

Example

a = 10
b = 0
result = a / b
print(result)

Output

ZeroDivisionError: division by zero

Here, Python cannot divide by zero, so it raises an exception.

Exceptions can occur due to invalid input, missing files, incorrect data types, or other runtime conditions.


4. Common Built-in Exceptions in Python

Python comes with a wide variety of built-in exceptions. Here are some of the most common ones:

ExceptionDescription
ZeroDivisionErrorRaised when dividing by zero
TypeErrorRaised when an operation is applied to an incorrect type
ValueErrorRaised when a function receives an argument of the right type but inappropriate value
NameErrorRaised when a variable is not defined
IndexErrorRaised when trying to access an index that does not exist
KeyErrorRaised when a key is not found in a dictionary
FileNotFoundErrorRaised when trying to open a non-existent file
AttributeErrorRaised when an invalid attribute reference is made
ImportErrorRaised when an import statement fails

These exceptions help you identify what went wrong and where, so you can handle it appropriately.


5. The Need for Exception Handling

Without proper exception handling, your program will crash whenever it encounters an error. This is undesirable, especially in real-world applications where stability and reliability are important.

For example, imagine a banking system that crashes when a user accidentally enters a wrong input. Instead of shutting down, the program should display a user-friendly message and continue running.

That’s where exception handling comes in — allowing developers to catch and handle errors gracefully.


6. The try and except Blocks

The try and except blocks form the core of Python’s exception handling system.

The idea is simple:

  • Code that might raise an exception goes inside the try block.
  • If an exception occurs, Python jumps to the except block.

Syntax

try:
# Code that might raise an exception
except ExceptionType:
# Code that handles the exception

7. Basic Example of try and except

try:
num = int(input("Enter a number: "))
result = 10 / num
print("Result:", result)
except ZeroDivisionError:
print("You cannot divide by zero.")
except ValueError:
print("Invalid input. Please enter a number.")

Explanation

  • The program asks for user input.
  • If the user enters 0, a ZeroDivisionError occurs.
  • If the user enters a non-numeric value, a ValueError occurs.
  • Each error is handled gracefully with a meaningful message.

8. Catching Multiple Exceptions

You can handle multiple exceptions in a single except block by grouping them in parentheses.

Example

try:
a = int(input("Enter a number: "))
b = int(input("Enter another number: "))
print("Result:", a / b)
except (ValueError, ZeroDivisionError):
print("Invalid input or division by zero occurred.")

This is useful when multiple exceptions require the same handling logic.


9. Catching All Exceptions

You can catch all exceptions using a generic except block without specifying an error type.

Example

try:
x = int(input("Enter a number: "))
y = 10 / x
except:
print("An error occurred.")

While this works, it’s generally not recommended because it hides useful error information. It’s better to catch specific exceptions whenever possible.


10. Using Exception as e to Get Error Details

You can also capture the exception object to display or log detailed error information.

Example

try:
x = int(input("Enter a number: "))
y = 10 / x
except Exception as e:
print("Error occurred:", e)

If you enter 0, the output will be:

Error occurred: division by zero

This approach helps you understand what went wrong and log it for debugging.


11. Using else with try-except

You can add an else block that runs only when no exceptions are raised in the try block.

Example

try:
num = int(input("Enter a number: "))
result = 10 / num
except ZeroDivisionError:
print("Cannot divide by zero.")
else:
print("Division successful. Result:", result)

Here, if no exception occurs, the else block executes.


12. Using finally for Cleanup Actions

The finally block is used to define code that should run no matter what happens — whether an exception occurs or not. It is often used for cleanup tasks like closing files or releasing resources.

Example

try:
file = open("example.txt", "r")
content = file.read()
print(content)
except FileNotFoundError:
print("File not found.")
finally:
file.close()
print("File closed.")

Even if an error occurs, the finally block ensures that the file is closed properly.


13. The Order of Execution in try, except, else, and finally

Python executes these blocks in a specific order:

  1. Code inside try executes first.
  2. If an exception occurs, Python jumps to the appropriate except block.
  3. If no exception occurs, the else block runs.
  4. The finally block always runs, regardless of what happens.

14. Nested try-except Blocks

You can have a try block inside another try block. This is called nested exception handling and is useful when you want to handle specific errors separately.

Example

try:
num = int(input("Enter a number: "))
try:
    result = 10 / num
    print("Result:", result)
except ZeroDivisionError:
    print("Cannot divide by zero inside nested block.")
except ValueError:
print("Invalid input.")

This allows more fine-grained control over error handling.


15. Raising Exceptions Manually with raise

Sometimes, you may want to trigger an exception intentionally when a certain condition is not met. You can do this using the raise keyword.

Example

def withdraw(amount):
if amount < 0:
    raise ValueError("Withdrawal amount cannot be negative.")
else:
    print("Amount withdrawn:", amount)
withdraw(-100)

Output

ValueError: Withdrawal amount cannot be negative.

Raising exceptions manually helps enforce logical rules and constraints in your program.


16. Creating Custom Exceptions

In addition to built-in exceptions, you can define your own custom exception classes to handle specific application-level errors.

Example

class NegativeNumberError(Exception):
"""Raised when a negative number is entered."""
pass
def check_number(num):
if num < 0:
    raise NegativeNumberError("Negative number entered.")
else:
    print("Number is positive.")
check_number(-10)

Custom exceptions make your code more descriptive and easier to debug.


17. Handling Custom Exceptions

You can handle custom exceptions the same way as built-in ones.

Example

try:
check_number(-5)
except NegativeNumberError as e:
print("Custom Exception Caught:", e)

Output:

Custom Exception Caught: Negative number entered.

18. Example: Exception Handling in File Operations

File operations are prone to many errors, such as missing files or permission issues. Exception handling ensures that the program continues to run smoothly.

Example

try:
file = open("data.txt", "r")
content = file.read()
print(content)
except FileNotFoundError:
print("The file does not exist.")
finally:
print("Operation completed.")

19. Example: Exception Handling in Division Program

Let’s handle division safely with exception handling.

try:
num1 = int(input("Enter numerator: "))
num2 = int(input("Enter denominator: "))
result = num1 / num2
except ZeroDivisionError:
print("Cannot divide by zero.")
except ValueError:
print("Invalid input. Please enter numbers only.")
else:
print("Result:", result)
finally:
print("Execution completed.")

This is a clean and safe approach that prevents crashes from user input errors.


20. Logging Exceptions

In real-world applications, it’s important to record exceptions in a log file for debugging and auditing. Python’s built-in logging module makes this easy.

Example

import logging

logging.basicConfig(filename="error.log", level=logging.ERROR)

try:
x = int("abc")
except Exception as e:
logging.error("Error occurred: %s", e)

This logs the error details into a file named error.log.


21. Re-raising Exceptions

Sometimes, after handling an exception partially, you might want to re-raise it to let other parts of the code handle it.

Example

try:
try:
    num = int("xyz")
except ValueError:
    print("Inner block handled ValueError.")
    raise
except ValueError:
print("Outer block caught re-raised exception.")

Output:

Inner block handled ValueError.
Outer block caught re-raised exception.

22. Suppressing Exceptions

In some cases, you may want to ignore specific exceptions intentionally. Python provides the contextlib.suppress() method for this.

Example

from contextlib import suppress

with suppress(FileNotFoundError):
open("non_existing_file.txt")

This code silently ignores the FileNotFoundError.


23. Assertions vs Exceptions

Assertions are used to test assumptions in code during development, while exceptions handle unexpected conditions at runtime.

Example with Assertion

x = 10
assert x > 0, "x must be positive"

If x is not greater than 0, Python raises an AssertionError.

Assertions are mainly for debugging, while exceptions are for real error handling.


24. Best Practices for Exception Handling

  1. Catch only the exceptions you expect.
  2. Avoid using bare except statements.
  3. Always clean up resources using finally or context managers.
  4. Use custom exceptions for domain-specific errors.
  5. Log exceptions instead of printing them in production.
  6. Don’t use exceptions for normal control flow.
  7. Keep try blocks small and focused.
  8. Document all custom exceptions.

Following these best practices ensures clean and predictable error management.


25. Example: Complete Program with Exception Handling

Scenario: ATM Withdrawal System

class InsufficientFundsError(Exception):
"""Raised when the account balance is insufficient."""
pass
def withdraw(balance, amount):
try:
    if amount > balance:
        raise InsufficientFundsError("Insufficient balance.")
    elif amount <= 0:
        raise ValueError("Invalid withdrawal amount.")
    else:
        balance -= amount
        print(f"Withdrawal successful. Remaining balance: {balance}")
except InsufficientFundsError as e:
    print("Error:", e)
except ValueError as e:
    print("Error:", e)
finally:
    print("Transaction complete.")
# Example usage withdraw(1000, 500) withdraw(1000, 2000) withdraw(1000, -100)

This program demonstrates multiple exception types, custom exceptions, and the use of finally for consistent output.


26. The Role of Exception Hierarchy

All exceptions in Python inherit from the base class BaseException. The most common parent class for user-defined exceptions is Exception.

Hierarchy Example:

BaseException
 ├── SystemExit
 ├── KeyboardInterrupt
 └── Exception
  ├── ArithmeticError
  ├── LookupError
  ├── ValueError
  ├── TypeError

Understanding this hierarchy helps you decide which exceptions to catch and how to structure your exception classes.


27. Using try-except in Functions

You can use exception handling inside functions to make them more robust.

Example

def safe_divide(a, b):
try:
    return a / b
except ZeroDivisionError:
    return "Division by zero error."
except TypeError:
    return "Invalid data type."
print(safe_divide(10, 2)) print(safe_divide(10, 0)) print(safe_divide("a", 2))

Each call handles its own potential issues without crashing the whole program.


28. Handling Exceptions in Loops

Exception handling within loops ensures that errors in one iteration don’t stop the entire loop.

Example

numbers = [10, 0, 'a', 5]

for n in numbers:
try:
    print(10 / n)
except Exception as e:
    print("Error:", e)

The loop continues even if one item causes an error.


29. Benefits of Exception Handling

  • Prevents program crashes
  • Improves user experience
  • Makes debugging easier
  • Enhances code readability and structure
  • Supports graceful error recovery
  • Encourages modular and reusable design

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *