Skip to content

Exception Handling in Python

Mastering Exception Handling in Python: A Comprehensive Guide

Errors are unavoidable in real programs — a file may be missing, a user may type text where a number was expected, or a network call may fail. Exception handling is the mechanism Python provides to detect these errors and respond to them gracefully instead of crashing. In this guide, we’ll explore the trytry, exceptexcept, elseelse, and finallyfinally blocks, the raiseraise keyword, and how to build your own custom exceptions.

What is an Exception?

An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. When Python encounters an error it cannot handle, it raises an exception. If the exception is not handled, the program stops and prints a traceback.

error.py
# An unhandled exception
print(10 / 0)
error.py
# An unhandled exception
print(10 / 0)

Output:

command
C:\Users\Your Name> python error.py
Traceback (most recent call last):
  File "error.py", line 2, in <module>
    print(10 / 0)
ZeroDivisionError: division by zero
command
C:\Users\Your Name> python error.py
Traceback (most recent call last):
  File "error.py", line 2, in <module>
    print(10 / 0)
ZeroDivisionError: division by zero

The program never reaches any code after line 2 — the ZeroDivisionErrorZeroDivisionError terminates it. Exception handling lets us catch that error and continue.

Errors vs. Exceptions

  • Syntax errors are mistakes in the structure of the code (a missing colon, an unclosed bracket). They are caught before the program runs and cannot be handled at runtime.
  • Exceptions are raised while the program runs. These are the errors we handle with trytry/exceptexcept.
syntax_error.py
# Syntax error - cannot be caught with try/except
if True
    print("missing colon")
syntax_error.py
# Syntax error - cannot be caught with try/except
if True
    print("missing colon")

The try-except Block

The trytry block contains code that might raise an exception. The exceptexcept block contains the code that runs if an exception occurs.

The syntax of the try-excepttry-except block in Python is as follows:

Syntax
try:
    # code that might raise an exception
except ExceptionType:
    # code that runs if the exception occurs
Syntax
try:
    # code that might raise an exception
except ExceptionType:
    # code that runs if the exception occurs

Diagram:

diagram try-except flow mermaid
Flow of the try-except block in Python

The following example demonstrates how to catch a ZeroDivisionErrorZeroDivisionError:

try_except.py
try:
    result = 10 / 0
    print(result)
except ZeroDivisionError:
    print("You cannot divide by zero!")
try_except.py
try:
    result = 10 / 0
    print(result)
except ZeroDivisionError:
    print("You cannot divide by zero!")

Output:

command
C:\Users\Your Name> python try_except.py
You cannot divide by zero!
command
C:\Users\Your Name> python try_except.py
You cannot divide by zero!

In the above example, the division raises a ZeroDivisionErrorZeroDivisionError. Python jumps to the matching exceptexcept block, prints the message, and the program continues normally instead of crashing.

Catching the Exception Object

You can capture the exception object itself using the asas keyword. This gives you access to the error message.

as_keyword.py
try:
    number = int("hello")
except ValueError as error:
    print("Conversion failed:", error)
as_keyword.py
try:
    number = int("hello")
except ValueError as error:
    print("Conversion failed:", error)

Output:

command
C:\Users\Your Name> python as_keyword.py
Conversion failed: invalid literal for int() with base 10: 'hello'
command
C:\Users\Your Name> python as_keyword.py
Conversion failed: invalid literal for int() with base 10: 'hello'

Handling Multiple Exceptions

A single trytry block can be followed by several exceptexcept blocks. Python runs the first one that matches the raised exception.

multiple.py
try:
    value = int(input("Enter a number: "))
    result = 100 / value
    print(result)
except ValueError:
    print("That was not a valid number.")
except ZeroDivisionError:
    print("You cannot divide by zero.")
multiple.py
try:
    value = int(input("Enter a number: "))
    result = 100 / value
    print(result)
except ValueError:
    print("That was not a valid number.")
except ZeroDivisionError:
    print("You cannot divide by zero.")

You can also handle several exception types in one block by passing a tuple:

tuple_except.py
try:
    risky_operation()
except (ValueError, TypeError, KeyError) as error:
    print("Something went wrong:", error)
tuple_except.py
try:
    risky_operation()
except (ValueError, TypeError, KeyError) as error:
    print("Something went wrong:", error)

The else Clause

The elseelse block runs only if no exception was raised in the trytry block. It is useful for code that should run after a successful trytry, keeping the trytry block focused on the risky operation alone.

else_clause.py
try:
    number = int("42")
except ValueError:
    print("Invalid number.")
else:
    print("Conversion succeeded:", number)
else_clause.py
try:
    number = int("42")
except ValueError:
    print("Invalid number.")
else:
    print("Conversion succeeded:", number)

Output:

command
C:\Users\Your Name> python else_clause.py
Conversion succeeded: 42
command
C:\Users\Your Name> python else_clause.py
Conversion succeeded: 42

The finally Clause

The finallyfinally block runs no matter what — whether or not an exception was raised, and even if a returnreturn statement executes. It is used for cleanup tasks such as closing files or releasing resources.

finally_clause.py
try:
    file = open("data.txt", "r")
    data = file.read()
except FileNotFoundError:
    print("File not found.")
finally:
    print("Cleaning up...")
finally_clause.py
try:
    file = open("data.txt", "r")
    data = file.read()
except FileNotFoundError:
    print("File not found.")
finally:
    print("Cleaning up...")

Diagram:

diagram try-except-else-finally mermaid
Full exception handling flow in Python

The raise Keyword

You can deliberately raise an exception using the raiseraise keyword. This is useful for signalling that something is wrong with the input to your function.

raise.py
def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    print("Age set to", age)
 
set_age(-5)
raise.py
def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    print("Age set to", age)
 
set_age(-5)

Output:

command
C:\Users\Your Name> python raise.py
Traceback (most recent call last):
  ...
ValueError: Age cannot be negative
command
C:\Users\Your Name> python raise.py
Traceback (most recent call last):
  ...
ValueError: Age cannot be negative

Re-raising an Exception

Inside an exceptexcept block, a bare raiseraise re-raises the current exception after you have done some work (such as logging).

reraise.py
try:
    risky()
except ValueError:
    print("Logging the error before re-raising...")
    raise
reraise.py
try:
    risky()
except ValueError:
    print("Logging the error before re-raising...")
    raise

Custom Exceptions

You can define your own exception types by subclassing the built-in ExceptionException class. Custom exceptions make your code more readable and let callers catch your specific error.

custom_exception.py
class InsufficientFundsError(Exception):
    """Raised when a withdrawal exceeds the available balance."""
    pass
 
def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(
            f"Cannot withdraw {amount}; balance is only {balance}"
        )
    return balance - amount
 
try:
    withdraw(100, 250)
except InsufficientFundsError as error:
    print("Transaction failed:", error)
custom_exception.py
class InsufficientFundsError(Exception):
    """Raised when a withdrawal exceeds the available balance."""
    pass
 
def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(
            f"Cannot withdraw {amount}; balance is only {balance}"
        )
    return balance - amount
 
try:
    withdraw(100, 250)
except InsufficientFundsError as error:
    print("Transaction failed:", error)

Output:

command
C:\Users\Your Name> python custom_exception.py
Transaction failed: Cannot withdraw 250; balance is only 100
command
C:\Users\Your Name> python custom_exception.py
Transaction failed: Cannot withdraw 250; balance is only 100

Common Built-in Exceptions

ExceptionRaised when
ValueErrorValueErrorA function gets an argument of the right type but wrong value (e.g. int("abc")int("abc")).
TypeErrorTypeErrorAn operation is applied to an object of the wrong type.
KeyErrorKeyErrorA dictionary key is not found.
IndexErrorIndexErrorA sequence index is out of range.
FileNotFoundErrorFileNotFoundErrorA file or directory is requested but does not exist.
ZeroDivisionErrorZeroDivisionErrorThe second argument of a division or modulo is zero.
AttributeErrorAttributeErrorAn attribute reference or assignment fails.
ImportErrorImportErrorAn importimport statement fails to find the module.

Conclusion

Exception handling lets your programs respond to errors gracefully instead of crashing. The trytry block holds risky code, exceptexcept handles failures, elseelse runs on success, and finallyfinally always runs for cleanup. The raiseraise keyword lets you signal errors, and custom exception classes make your error handling expressive and precise. Master these tools and your Python programs will be far more robust. For more hands-on examples and in-depth tutorials, explore our resources on Python Central Hub!


Try it: Exception Handling Exercises

Exercise 1 – Catch a ZeroDivisionError

Exercise 2 – try / except / else / finally

Exercise 3 – Raise a Custom Exception

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did