☰

Python Exception Handling Tutorial

Python Exception Handling Tutorial: try, except, else and finally

A program that works on your own test data can still break the moment a real user touches it. Someone types a word where you expected a number. A file is missing. A network call times out. A list is shorter than you thought. Without any protection, Python stops at the first problem and prints a long message that means nothing to the user.

Exception handling is how you stay in control when this happens. You tell Python which lines might fail, what to do if they do, and what must always run afterward, such as closing a file. Done well, a program turns a crash into a clear message or a safe fallback, and it keeps running.

In this tutorial, we explain what an error and an exception are, and how Python reacts when one is not handled. We then go through the try and except blocks, handling more than one error, the else and finally blocks, the raise statement, custom exceptions, and file errors. Every program has been run. We finish with the order of except blocks, the exception hierarchy, and the mistakes to avoid.

Python exception handling tutorial with try, except, else and finally

What is an Error in Python?

Python has two kinds of errors.

  • Syntax errors are mistakes in how the code is written, such as a missing bracket. Python finds them before the program runs, and the program does not start at all.
  • Exceptions are errors that happen while the program is running, even though the code is written correctly. Dividing by zero is one example. Python stops the current line, shows a message, and ends the program, unless you handle it.
python
try:
    compile("print('hello'", "<test>", "exec")
except SyntaxError as e:
    print("SyntaxError:", e.msg)
output
SyntaxError: '(' was never closed

The difference matters, because try and except can handle exceptions, and a syntax error is something you fix in the code.

Common Built In Exceptions

These are the exceptions you will meet most, each with the real message Python gives.

Common exceptions and their messages
Exception When it happens Real message
ZeroDivisionError Dividing by zero division by zero
NameError Using a name that does not exist name ‘undefined_name’ is not defined
IndexError A list position is out of range list index out of range
KeyError A dictionary key is missing ‘b’
ValueError Right type, wrong value invalid literal for int() with base 10: ‘abc’
TypeError Wrong type for an operation can only concatenate str (not “int”) to str
FileNotFoundError The file does not exist [Errno 2] No such file or directory

The Effect of Exception Handling

An exception changes the normal flow of a program. When one is raised, Python stops running the lines in order and looks for a handler. If it finds an except block that matches, it runs that block and carries on after the whole try statement. If it finds none, the program ends right away and prints a traceback.

How Python Handles an Exception

Python reads the try block line by line. If no line fails, the except blocks are skipped. If a line fails, Python jumps out of the try block at once, and the lines after the failing one do not run. It then picks the first except block that matches the error type.

Handling exceptions gives you three real benefits.

  • Cleaner code. The normal path and the error path are separate, so you do not wrap every line in an if check.
  • One consistent way to deal with errors. Every library you use reports problems in the same way.
  • A program that does not crash. It can show a clear message, use a default, or try again.

Exception Handling Example

The first print works. The second asks for index 4 in a list that has only four items, so Python raises an IndexError, and the except block runs.

python
test = [1, 2, 4, 8]
try:
    print("First element = %d" % (test[0]))
    # Only 4 elements, so index 4 raises an IndexError
    print("Fifth element = %d" % (test[4]))
except IndexError:
    print("Error occurred")
output
First element = 1
Error occurred

Notice that the first line printed before the error, because the lines before the failure run normally.

Reading the Error Message

You can catch the error as a variable with the word as, and print the message Python gives. This helps when you need to know what went wrong.

python
try:
    int("abc")
except ValueError as e:
    print("Error:", e)
output
Error: invalid literal for int() with base 10: 'abc'

Multiple Exception Handling Example

A block of code can fail in more than one way. You have two choices. You can catch several types in one except line, by putting them in round brackets as a tuple. Or you can write a separate except block for each type, so each error gets its own response.

One Handler for Several Errors

The brackets are required. This example can fail in both ways. For the value 5, the division is by zero. For the text seven, Python cannot subtract a number from text.

python
def calculate(vari):
    try:
        cal = 100 / (vari - 5)
        print("value of cal =", cal)
    except (ZeroDivisionError, TypeError):
        print("Error handled")
 
calculate(5)
calculate(7)
calculate("seven")
output
Error handled
value of cal = 50.0
Error handled

A Separate Handler for Each Error

When the right response is different for each error, give each its own block. Python checks them from top to bottom and runs only the first match.

python
def divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("You cannot divide by zero")
    except TypeError:
        print("Both values must be numbers")
    else:
        print("Result:", result)
    finally:
        print("Division attempted")
 
divide(10, 2)
divide(10, 0)
divide(10, "2")
output
Result: 5.0
Division attempted
You cannot divide by zero
Division attempted
Both values must be numbers
Division attempted

At most one except block runs for each error.

The else and finally Statements

Two optional blocks make a try statement more useful.

else and finally
Block When it runs Typical use
else Only when the try block raised no exception Code that should run only if everything worked
finally Always, whether or not there was an error Cleanup, such as closing a file or a connection

The function below turns an age into days. For 20, nothing fails, so else and finally both run. For 20.5, the conversion to an int raises a ValueError, so the except block runs and then finally runs.

python
def print_age_in_days(years):
    print("Your age in days is about", 365 * int(years))
 
def check_age(age):
    try:
        print_age_in_days(age)
    except ValueError:
        print("You did not enter an age as a whole number")
    else:
        print(age, "successfully converted into days")
    finally:
        print("Input test complete")
 
check_age("20")
check_age("20.5")
output
Your age in days is about 7300
20 successfully converted into days
Input test complete
You did not enter an age as a whole number
Input test complete

In a real program the age would come from the input function. We pass it in as text here so the example runs without waiting for typing.

Raise an Exception in Python

So far Python raised the errors. You can also raise one yourself, with the raise statement. This is how you reject bad data early, with a clear message, before it causes a confusing problem later.

The raise Statement

python
def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return age
 
try:
    set_age(-5)
except ValueError as e:
    print("Error:", e)
output
Error: Age cannot be negative

The plainest form raises a NameError by hand, just to show the syntax:

python
try:
    raise NameError("Python code")
except NameError as e:
    print("Exception:", e)
output
Exception: Python code

Raising the Same Error Again

A bare raise, with nothing after it, inside an except block, sends the same exception up to the next handler. If there is no outer handler, Python runs the inner block and then stops with a traceback ending in NameError: Python code. With an outer handler, it works as you would expect:

python
try:
    try:
        raise NameError("Python code")
    except NameError:
        print("Inner handler ran")
        raise
except NameError as e:
    print("Outer handler caught:", e)
output
Inner handler ran
Outer handler caught: Python code

Use this when a function wants to log or clean up after an error, and still let the caller decide what to do.

Create Your Own Exception

When the built in exceptions do not describe your problem, make your own by writing a class that inherits from Exception. It gives your error a clear name that the rest of the program can catch.

python
class LowBalanceError(Exception):
    pass
 
def withdraw(balance, amount):
    if amount > balance:
        raise LowBalanceError(f"Balance {balance} is less than {amount}")
    return balance - amount
 
try:
    withdraw(500, 800)
except LowBalanceError as e:
    print("Custom error:", e)
output
Custom error: Balance 500 is less than 800

Opening Files and Exceptions

Files are the most common place where exceptions happen, because a file can be missing, locked, or without permission. Opening a file that does not exist raises a FileNotFoundError. This is a kind of OSError. In Python 3, the name IOError is only another name for OSError.

The error object carries useful details. The attribute errno is the error number, strerror is the message, and filename is the file that caused it.

python
try:
    infile = open("Incorrect filename")
except OSError as ioe:
    print("Unable to open the file")
    print("Error number", ioe.errno)
    print("Message", ioe.strerror)
    print("Filename in error", ioe.filename)
output
Unable to open the file
Error number 2
Message No such file or directory
Filename in error Incorrect filename

Use with to Close Files Safely

The with statement closes the file for you, even when an error happens, so you do not need a finally block just for that.

python
try:
    with open("notes_missing.txt") as f:
        data = f.read()
except FileNotFoundError:
    print("File not found, using default text")
output
File not found, using default text

The Exception Hierarchy

Exceptions are arranged in a family tree. Every exception inherits from BaseException. Almost all the errors you meet inherit from Exception, which sits just below it. So Exception is not the top of the tree.

python
print(ZeroDivisionError.__mro__)
print(issubclass(KeyError, LookupError), issubclass(IndexError, LookupError))
output
(<class 'ZeroDivisionError'>, <class 'ArithmeticError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)
True True

The first line reads from the left: ZeroDivisionError is a kind of ArithmeticError, which is a kind of Exception, which is a kind of BaseException. KeyError and IndexError are both kinds of LookupError. That means catching the parent catches all its children.

Why the Order of except Blocks Matters

Python runs the first except block that matches. If a parent comes before its child, the child block can never run.

python
try:
    [1, 2][5]
except LookupError:
    print("Caught by the parent first")
except IndexError:
    print("Never reached")
output
Caught by the parent first

Put the most specific exception first, and the general ones after.

Best Practices and Common Mistakes

  • Do not use a bare except. An except line with no type catches everything, even a user pressing Ctrl and C, and hides real bugs. Name the errors you expect.
  • Keep the try block small. Put only the risky lines inside it, so you know which line failed.
  • Do not silence errors. An empty except block with only pass makes bugs invisible. At least print or log the message.
  • Catch the specific error. Use ValueError for bad input and FileNotFoundError for a missing file, not the general Exception.
  • Use finally or with for cleanup. Files and connections should close, even when something fails.
  • Order the except blocks from specific to general.
  • Raise your own errors early. Check the input at the start of a function and raise a clear error, instead of letting it fail somewhere confusing.
  • Watch the spelling and the colon. Writing exceptValueError, or leaving the colon off an except line, is a syntax error, and Python will not even start the program.

Quick Reference

Exception handling keywords
Keyword What it does
try Holds the code that might fail
except Runs if a matching error happens, and can name the error type
else Runs only if the try block had no error
finally Always runs, used for cleanup
raise Raises an exception yourself, or raises the current one again
as Gives a name to the error object, such as except ValueError as e

Complete Script to Run Yourself

Here is the full script used to produce every output on this page. Copy it into a file, run it with Python 3, and compare the results line by line.

python
# 1. The basic try and except
test = [1, 2, 4, 8]
try:
    print("First element = %d" % (test[0]))
    # Only 4 elements, so index 4 raises an IndexError
    print("Fifth element = %d" % (test[4]))
except IndexError:
    print("Error occurred")
 
# 2. What an unhandled error looks like (we catch it only to show the message)
for label, func in [
    ("ZeroDivisionError", lambda: 10 / 0),
    ("NameError", lambda: undefined_name),
    ("IndexError", lambda: [1, 2, 3][5]),
    ("KeyError", lambda: {"a": 1}["b"]),
    ("ValueError", lambda: int("abc")),
    ("TypeError", lambda: "5" + 5),
    ("FileNotFoundError", lambda: open("no_such_file.txt")),
]:
    try:
        func()
    except Exception as e:
        print(label, "->", e)
 
# 3. Handling several exceptions
def calculate(vari):
    try:
        cal = 100 / (vari - 5)
        print("value of cal =", cal)
    except (ZeroDivisionError, TypeError):
        print("Error handled")
 
calculate(5)
calculate(7)
calculate("seven")
 
# 4. Separate handlers for separate errors
def divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("You cannot divide by zero")
    except TypeError:
        print("Both values must be numbers")
    else:
        print("Result:", result)
    finally:
        print("Division attempted")
 
divide(10, 2)
divide(10, 0)
divide(10, "2")
 
# 5. else and finally
def print_age_in_days(years):
    print("Your age in days is about", 365 * int(years))
 
def check_age(age):
    try:
        print_age_in_days(age)
    except ValueError:
        print("You did not enter an age as a whole number")
    else:
        print(age, "successfully converted into days")
    finally:
        print("Input test complete")
 
check_age("20")
check_age("20.5")
 
# 6. The raise statement
try:
    raise NameError("Python code")
except NameError as e:
    print("Exception:", e)
 
# 7. Raising again with a bare raise, caught by an outer handler
try:
    try:
        raise NameError("Python code")
    except NameError:
        print("Inner handler ran")
        raise
except NameError as e:
    print("Outer handler caught:", e)
 
# 8. Raising your own error with a check
def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return age
 
try:
    set_age(-5)
except ValueError as e:
    print("Error:", e)
 
# 9. Custom exception
class LowBalanceError(Exception):
    pass
 
def withdraw(balance, amount):
    if amount > balance:
        raise LowBalanceError(f"Balance {balance} is less than {amount}")
    return balance - amount
 
try:
    withdraw(500, 800)
except LowBalanceError as e:
    print("Custom error:", e)
 
# 10. Opening files
try:
    infile = open("Incorrect filename")
except OSError as ioe:
    print("Unable to open the file")
    print("Error number", ioe.errno)
    print("Message", ioe.strerror)
    print("Filename in error", ioe.filename)
 
print(IOError is OSError)
print(FileNotFoundError.__mro__[1].__name__)
 
# 11. with closes the file for you
try:
    with open("notes_missing.txt") as f:
        data = f.read()
except FileNotFoundError:
    print("File not found, using default text")
 
# 12. The exception hierarchy
print(ZeroDivisionError.__mro__)
print(Exception.__mro__)
print(issubclass(KeyError, LookupError), issubclass(IndexError, LookupError))
 
# 13. Order of except blocks matters
try:
    [1, 2][5]
except LookupError:
    print("Caught by the parent first")
except IndexError:
    print("Never reached")
 
# 14. Catching every error, and why not to
try:
    int("abc")
except Exception as e:
    print(type(e).__name__, "-", e)
 
# 15. Getting the full traceback text
import traceback
try:
    1 / 0
except ZeroDivisionError:
    print(traceback.format_exc().strip().splitlines()[-1])
 
# 16. Syntax errors are not caught at run time
try:
    compile("print('hello'", "<test>", "exec")
except SyntaxError as e:
    print("SyntaxError:", e.msg)

Exception handling is less about stopping errors and more about deciding what happens next. Catch the specific errors you expect, give the user a clear message, clean up with finally or with, and raise your own errors when the data is wrong. Run the script above, break each example on purpose, and read the messages. Then move on to Python file handling and modules, where these habits are used all the time.

Popular Tags:

Best Python Training in BTM Layout Python Exception Handling Tutorial python training in btm