Every Python program eventually encounters something unexpected — a file that does not exist, a number that cannot be divided, or input that is not what the code expected. Exception handling gives you a structured way to respond to those situations without the program crashing.
Python separates two categories of problems: syntax errors, which are caught before your code ever runs, and exceptions, which occur during execution. This tutorial focuses entirely on exceptions — what they are, how to catch them, and how to raise them yourself when your own code needs to signal a problem.
What Is an Exception?
An exception is an event that interrupts the normal flow of a program. When Python cannot complete an operation — for example, dividing by zero or opening a file that does not exist — it creates an exception object describing what went wrong and stops executing the current block of code.
If nothing handles the exception, Python terminates the program and prints a traceback. A traceback tells you the exception type, a message explaining the problem, and the exact line where execution stopped. Reading tracebacks carefully is one of the first practical skills to develop as a Python programmer.
# This line raises a ZeroDivisionError
result = 10 / 0
# Output:
# Traceback (most recent call last):
# File "example.py", line 2, in <module>
# result = 10 / 0
# ZeroDivisionError: division by zero
The traceback shows the exception type on the last line — ZeroDivisionError — followed by a colon and a human-readable message. The lines above it show the call chain that led to the error.
In Python, exceptions are objects. Every built-in exception inherits from the base class BaseException. The exceptions you will handle day-to-day — ValueError, TypeError, FileNotFoundError, and others — all inherit from Exception, which itself inherits from BaseException.
The try and except Blocks
The try block marks code that might raise an exception. The except block defines what should happen if it does. Python attempts to run the code inside try. If an exception is raised, Python jumps immediately to the matching except clause and runs that instead.
try:
number = int(input("Enter a number: "))
print(10 / number)
except ZeroDivisionError:
print("You cannot divide by zero.")
except ValueError:
print("That was not a valid number.")
In the example above, two different exceptions can occur. If the user enters 0, Python raises ZeroDivisionError. If the user enters something that cannot be converted to an integer — like hello — Python raises ValueError. Each except clause handles one specific type.
You can capture the exception object itself by writing except ValueError as e:. The variable e holds the exception instance, and str(e) gives you the error message. This is useful for logging exactly what went wrong.
Catching Multiple Exception Types at Once
If two exception types should trigger the same response, you can group them in a tuple inside a single except clause rather than repeating yourself.
try:
value = int(user_input) / divisor
except (ValueError, ZeroDivisionError) as e:
print(f"Input problem: {e}")
Build a try-except block that catches a ValueError when converting user input to an integer:
try: to open the guarded block, then int(user_input) as the risky operation (indented), then except followed by ValueError: (the specific type), then the indented handler. finally: is a different clause — it is not used here. TypeError: handles wrong types, not bad string-to-int conversions.
Common Built-in Exception Types
Python ships with dozens of built-in exception types. Knowing the ones you will encounter regularly makes it much easier to write targeted except clauses. The table below covers the exceptions absolute beginners run into the most.
- When raised
- The argument is the right type but an unacceptable value — for example,
int("hello")ormath.sqrt(-1). - Common cause
- Converting user input that does not match the expected format.
- When raised
- An operation is applied to an object of the wrong type — for example,
"2" + 2or calling a non-callable object. - Common cause
- Mixing incompatible types in arithmetic or function calls without explicit conversion.
- When raised
- The divisor in a division or modulo operation is zero —
x / 0orx % 0. - Common cause
- Dynamic divisors calculated from user input or data that may be zero.
- When raised
- A sequence index is out of range — for example, accessing
my_list[10]when the list only has five elements. - Common cause
- Off-by-one errors in loops, or assuming a list has at least a certain number of items.
- When raised
- A dictionary lookup uses a key that does not exist in the dictionary —
my_dict["missing"]. - Common cause
- Accessing dictionary keys derived from external data without first checking whether the key is present.
- When raised
- A file-open or file-access operation targets a path that does not exist —
open("missing.txt"). - Common cause
- Hardcoded file paths, typos in filenames, or files that may have been moved or deleted at runtime.
- When raised
- An attribute reference or assignment fails because the object does not have that attribute —
None.upper(). - Common cause
- Functions that can return
Nonewhen the result is used without checking, or typos in method names.
This function tries to safely convert a string to an integer. One line contains a bug. Click it, then hit check.
except TypeError: to except ValueError:. When int() receives a string that cannot be parsed — like "hello" — it raises a ValueError, not a TypeError. A TypeError would be raised if you passed in an incompatible type entirely (such as a list), which is a different situation. Catching the wrong exception type means the real error goes unhandled.
The else and finally Clauses
Two optional clauses extend what you can do with a try-except block: else and finally. Neither is required, but each serves a distinct purpose.
The else Clause
The else block runs only when the try block completes without raising any exception. This lets you keep the success-path logic clearly separate from both the risky operation and the error-handling code.
try:
f = open("data.txt", "r")
except FileNotFoundError:
print("File not found.")
else:
# Runs only if open() succeeded
content = f.read()
print(f"Read {len(content)} characters.")
f.close()
The finally Clause
The finally block always runs — whether the try block succeeded, raised a caught exception, or raised an uncaught one. It is the right place for cleanup code that must execute no matter what happens, such as closing a file handle or releasing a network connection.
try:
result = 10 / int(input("Divisor: "))
except (ValueError, ZeroDivisionError) as e:
print(f"Error: {e}")
else:
print(f"Result: {result}")
finally:
print("Calculation attempt complete.")
Avoid using a bare except: clause with no exception type specified. It catches everything, including SystemExit and KeyboardInterrupt, which makes the program very hard to stop and can mask real bugs. Always name at least one specific exception type.
Using raise to Trigger Exceptions Intentionally
You are not limited to catching exceptions that Python raises automatically. The raise keyword lets you signal that something has gone wrong in your own code — for example, when a function receives an argument that passes type checks but violates a business rule.
def set_age(age):
if not isinstance(age, int):
raise TypeError("age must be an integer")
if age < 0:
raise ValueError("age cannot be negative")
return age
try:
set_age(-5)
except ValueError as e:
print(f"Invalid age: {e}")
Inside an except block you can also use raise with no arguments to re-raise the current exception after logging it or doing partial cleanup, without swallowing it.
"Errors should never pass silently. Unless explicitly silenced." — The Zen of Python, Tim Peters
How to Use Exception Handling in Python
The five steps below walk through the complete structure of a well-formed exception handler, from wrapping risky code to releasing resources in finally.
-
Wrap risky code in a try block
Place only the code that might raise an exception inside the
tryblock. Keep this block as small and focused as possible — wrapping large amounts of code makes it harder to know which line actually caused a problem. -
Catch the exception with a named except clause
Write
except SpecificExceptionType as e:to handle a known error. Usingas elets you inspect the error message withstr(e)or log it. Always name the exception type — avoid bareexcept:which catches everything including system signals. -
Add an else block for code that should run only on success
Place logic that depends on the
tryblock having succeeded in theelseclause. This keeps your success path visually distinct from error-handling code and avoids accidentally catching exceptions raised inside that success logic. -
Use finally for guaranteed cleanup
Put resource-release code — closing files, network connections, or locks — in a
finallyblock. It runs in every scenario: after a successfultry, after a caught exception, and even after an uncaught exception before the program unwinds. -
Raise exceptions intentionally when your code detects invalid state
Use
raise ValueError("descriptive message")or another appropriate exception type when your function receives bad input. This tells callers exactly what went wrong and gives them the opportunity to handle it with their owntry-exceptblock.
Python Exception Handling Summary Points
- The
tryblock contains code that might fail. Python executes it normally unless an exception is raised, in which case it jumps immediately to the matchingexceptclause. - Always name the exception type in an
exceptclause. Catching broad exceptions hides bugs; catching specific types likeValueErrororFileNotFoundErrorkeeps the handler focused on the problem it was designed for. - The
elseclause runs only when thetryblock completes without error. Thefinallyclause always runs, making it the right place for cleanup that must not be skipped under any circumstances. - You can raise exceptions deliberately with the
raisekeyword. Inside anexceptblock, a bareraisere-raises the current exception, which is useful when you want to log an error without silently swallowing it. - Knowing the common built-in exception types —
ValueError,TypeError,ZeroDivisionError,IndexError,KeyError,FileNotFoundError, andAttributeError— lets you write targeted handlers rather than guessing which exception to catch.
Exception handling is not just about preventing crashes. It is about writing code that communicates what went wrong and gives the program a clear path to recover or fail gracefully. Start with the simplest try-except structure, name your exception types, and keep the guarded blocks small.
Frequently Asked Questions
Exception handling in Python is a way to respond to runtime errors without crashing your program. When Python encounters an error it cannot resolve automatically, it raises an exception. You can catch that exception using a try-except block and define what should happen instead of a crash.
The try block contains code that might raise an exception. The except block runs if an exception is raised. The else block runs only if no exception was raised in the try block. The finally block always runs regardless of whether an exception occurred, making it useful for cleanup tasks like closing files.
Name the exception type after the except keyword — for example, except ValueError:. You can catch multiple types in one clause by listing them in a tuple: except (ValueError, TypeError):. Catching specific types is better practice than using a bare except: clause.
The raise keyword lets you trigger an exception intentionally. You can raise a built-in exception such as raise ValueError("age must be positive") or re-raise the current exception inside an except block by writing just raise with no arguments.
A ValueError is raised when a function receives an argument of the right type but an inappropriate value. For example, int('hello') raises a ValueError because the string 'hello' cannot be converted to an integer even though a string is a valid type for that argument.
A TypeError is raised when an operation is applied to an object of an unsuitable type. For example, adding a string to an integer like '2' + 2 raises a TypeError because Python cannot implicitly convert between those types.
Yes. A single try block can have multiple except clauses, each handling a different exception type. Python checks them in order from top to bottom and runs the first one that matches. Only one except block executes per exception.
The finally block is used for cleanup code that must run no matter what. Common uses include closing file handles, releasing network connections, and releasing locks. It runs whether the try block succeeded, raised a caught exception, or raised an uncaught exception.
If an exception is not caught by any except block, it propagates up the call stack. If it reaches the top level without being handled, Python terminates the program and prints a traceback showing the exception type, message, and the line where it occurred.
A bare except: clause catches every exception including SystemExit and KeyboardInterrupt, which can make programs difficult to stop and hide genuine bugs. It is better practice to catch specific exception types or at minimum use except Exception: which excludes system-level exceptions.