Skip to content
PC Moment
Programming

Python Raise Exception: How to Use raise, Custom Exceptions and raise from

How to raise exceptions in Python: built-in errors like ValueError, custom exception classes, raise from, re-raising and when to use assert instead.

By PC MomentUpdated 7 min read

PC Moment title card for Python Raise Exception

To raise an exception in Python, use the raise statement with an exception object, such as raise ValueError("age must be 0 or more"). Python stops running the current code and looks for a matching except block. If it doesn’t find one, the program ends with a traceback that shows your message.

This guide covers the basic syntax, which built-in exception to pick, custom exceptions, raise ... from, re-raising, and when assert is the wrong tool. All examples run on Python 3.12 and later unless a section says otherwise.

The basic raise syntax

Raise an exception when your function receives something it can’t work with:

def set_age(age):
    if age < 0:
        raise ValueError(f"age must be 0 or more, got {age}")
    return age

set_age(-3)

Running this ends with a traceback whose last line is:

ValueError: age must be 0 or more, got -3

The thing after raise must be an exception instance or an exception class (a class that derives from BaseException). If you pass a class, Python creates the instance for you by calling it with no arguments, so raise ValueError is shorthand for raise ValueError(). In practice, always pass a message: it’s the first thing whoever reads the traceback will see.

Which built-in exception should you raise?

Pick the most specific built-in exception that fits. The Python docs define these common ones:

ExceptionRaise it when
ValueErrorAn argument has the right type but an inappropriate value (a negative age, an empty name)
TypeErrorAn argument has the wrong type (a list where an int is expected)
KeyErrorA key isn’t found in a dictionary-like mapping
IndexErrorA sequence index is out of range
NotImplementedErrorA method in a base class must be overridden by subclasses, or isn’t written yet
FileNotFoundErrorA file or directory you asked for doesn’t exist

The docs give a simple rule for the two you’ll use most: passing an argument of the wrong type should raise TypeError, and passing the right type with a wrong value should raise ValueError.

def set_age(age):
    if not isinstance(age, int):
        raise TypeError(f"age must be an int, not {type(age).__name__}")
    if age < 0:
        raise ValueError(f"age must be 0 or more, got {age}")
    return age

Catch the exception with try and except

Code that calls your function can handle the error with try and except. Use as to get the exception object, and print() it to see the message:

try:
    set_age("ten")
except TypeError as err:
    print("Type problem:", err)

try:
    set_age(-3)
except ValueError as err:
    print("Value problem:", err)

Output:

Type problem: age must be an int, not str
Value problem: age must be 0 or more, got -3

Only one handler runs for each exception. If no except clause matches, the exception keeps travelling up to the code that called this one.

Add else and finally

A full try statement can have four parts:

  • try: the code that might fail.
  • except: runs if a matching exception is raised.
  • else: runs only if the try block raised nothing.
  • finally: runs last, whether or not an exception happened.
def read_number(text):
    try:
        number = int(text)
    except ValueError:
        print(f"{text!r} is not a whole number")
        return None
    else:
        print("Parsed", number)
        return number
    finally:
        print("Done checking", repr(text))

read_number("42")
read_number("4.2")

Output:

Parsed 42
Done checking '42'
'4.2' is not a whole number
Done checking '4.2'

The finally block runs even though both branches return. The Python tutorial recommends else over putting more code inside try, because it avoids accidentally catching an exception that wasn’t raised by the code you meant to protect.

Note: Don’t put return, break or continue inside finally. They swallow any exception that was on its way out, and Python 3.14 and later show a SyntaxWarning for them.

For cleanup like closing files, a with statement is usually simpler than finally: the file is closed even if an error happens while you work with it.

Create a custom exception

When a built-in exception doesn’t describe the problem well, define your own. The Python docs say user-defined exceptions should derive from Exception (not BaseException), and most exception names end in “Error”.

class InsufficientFundsError(Exception):
    """Raised when a withdrawal is larger than the balance."""

    def __init__(self, balance, amount):
        super().__init__(f"cannot withdraw {amount}; balance is {balance}")
        self.balance = balance
        self.amount = amount


def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount


try:
    withdraw(50, 80)
except InsufficientFundsError as err:
    print(err)
    print("Short by", err.amount - err.balance)

Output:

cannot withdraw 80; balance is 50
Short by 30

Passing the message to super().__init__() gives you a readable error, and storing balance and amount as attributes lets the handler use the numbers. Keep custom exceptions simple: often a class with just a docstring is enough.

Tip: If your module has several custom errors, create one base class (for example class BankError(Exception)) and derive the others from it. Callers can then catch every error from your module with except BankError.

Chain exceptions with raise from

Sometimes you catch a low-level error and want to raise a clearer one. Use raise ... from so the original error isn’t lost:

class ConfigError(Exception):
    pass


def get_port(settings):
    try:
        return int(settings["port"])
    except KeyError as err:
        raise ConfigError("settings has no 'port' entry") from err

get_port({})

The traceback shows both exceptions, joined by a line that explains how they relate:

KeyError: 'port'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  ...
ConfigError: settings has no 'port' entry

The original KeyError is stored in the new exception’s __cause__ attribute.

If you raise a new exception inside an except block without from, Python still links them, but the traceback says “During handling of the above exception, another exception occurred”. That wording reads like a second, unexpected error, so use from err when the new exception is a deliberate translation of the first.

Hide the original with from None

When the original error is just noise, use from None:

def get_port(settings):
    try:
        return int(settings["port"])
    except KeyError:
        raise ConfigError("settings has no 'port' entry") from None

Now the traceback shows only ConfigError. The docs note the original exception is still available in __context__ if you need it while debugging.

Re-raise the current exception

A bare raise inside an except block re-raises the exception you’re handling. It’s useful when you want to log or print something but still let the error stop the program:

import json

def load_settings(path):
    try:
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        print(f"Could not find {path}")
        raise

Calling load_settings("missing.json") prints the message, then the original FileNotFoundError continues with its full traceback.

Outside an except block there is nothing to re-raise, so a bare raise fails with:

RuntimeError: No active exception to reraise

Add context with add_note()

Since Python 3.11, every exception has an add_note() method. Notes appear in the traceback after the exception message, which is handy when you want to add context without changing the exception type:

try:
    raise ValueError("bad row")
except ValueError as err:
    err.add_note("while reading line 12 of data.csv")
    raise

The traceback ends with:

ValueError: bad row
while reading line 12 of data.csv

assert vs raise

assert looks like a shortcut for raising an error:

def apply_discount(price, percent):
    assert 0 <= percent <= 100, "percent must be between 0 and 100"
    return price * (100 - percent) / 100

With a normal run, apply_discount(80, 150) fails with AssertionError: percent must be between 0 and 100. But the Python reference explains that no code is generated for an assert statement when optimization is requested with the -O command-line option. Run the same file with python -O and the check vanishes: the function returns -40.0.

Use each for its own job:

  • raise for anything that must always be checked: user input, function arguments, file contents, API responses.
  • assert for internal sanity checks while developing, things that should be impossible if your code is correct.

Common mistakes

Raising a string. In Python 3 you can only raise exceptions. raise "something went wrong" fails with TypeError: exceptions must derive from BaseException. Wrap the message: raise RuntimeError("something went wrong").

Raising the generic Exception. raise Exception("bad value") works, but callers can only catch it with except Exception, which also catches every other error. Raise a specific built-in or custom exception instead.

Catching too broadly. except Exception: around a large block hides bugs you didn’t expect. Catch the exceptions you know how to handle and let the rest show up.

Losing the original error. Raising a new exception in an except block without from makes the traceback harder to read. Use raise NewError(...) from err.

Inheriting from BaseException. The docs say BaseException isn’t meant to be inherited directly by your own classes. A subclass of it isn’t an Exception, so a normal except Exception handler won’t catch it. Inherit from Exception instead.

Practice it

The best way to get comfortable with exceptions is to use them in a real program. The to-do list and expense tracker in our Python projects for beginners both crash on bad input in their starter versions, which makes them a perfect place to add try/except and your own raise statements.

If you’re still building your foundations, our path for how to learn Python for free points to free courses that cover exceptions in depth, and the Programming section has more beginner guides. Coming from Java? Our guide to Java printf covers formatting output in that language.

Frequently asked questions

How do I raise an exception in Python?

Use the raise statement with an exception instance, for example raise ValueError("age must be 0 or more"). The message you pass appears on the last line of the traceback.

What is the difference between raise and raise from?

raise from sets the original exception as the new one's cause, so the traceback shows both with the line "The above exception was the direct cause of the following exception". raise ... from None hides the original exception from the traceback.

Should a custom exception inherit from Exception or BaseException?

From Exception. The Python docs say user-defined exceptions should derive from Exception, and that BaseException is not meant to be inherited directly by user-defined classes.

Can I use assert instead of raise to check input?

Not for input you need to validate. Assert statements produce no code when Python runs with the -O option, so the check silently disappears. Use raise for checks that must always run.

What does a bare raise do?

Inside an except block, a bare raise re-raises the exception currently being handled. Outside one, it fails with RuntimeError because there is no active exception.

Related guides