How to throw an exception

Series: Exceptions
Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
5 minute read Python 3.7—3.10
Share
Copied to clipboard.

Let's talk about how to raise an exception in Python.

A function that raises an exception

Here we have a program called is_prime:

from math import sqrt


def is_prime(number):
    for candidate in range(2, int(sqrt(number))+1):
        if number % candidate == 0:
            return False
    return True

When we pass a negative number to the is_prime function, it will pass that negative number into the math.sqrt function, which raises a ValueError exception:

>>> is_prime(-2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/trey/is_prime.py", line 5, in is_prime
    for candidate in range(2, int(sqrt(number))+1):
ValueError: math domain error

The sqrt function in Python's math module raises an exception when it's given a negative number.

Sometimes exceptions are more appropriate than bad data

There are probably some cases where is_prime should be raising an exception but isn't right now. For example, if we pass a floating point number to the is_prime function, it returns an answer:

>>> is_prime(4.0)
False

And sometimes it returns a really weird answer:

>>> is_prime(4.5)
True

It doesn't make sense to ask a non-integer whether it's prime, so our is_prime function should probably raise an exception when given a floating point number.

Also, if we pass in 0 or 1 to is_prime, it returns True:

>>> is_prime(1)
True

It should probably either return False or raise an exception instead (depending on how we'd prefer to implement is_prime).

Raising an exception when a specific condition is met

We could modify our is_prime function to check for these two conditions, raising an exception if either of those conditions are met.

First we'll ask whether the given number is an instance of the float class, and we'll raise a TypeError exception if it is:

    if isinstance(number, float):
        raise TypeError(f"Only integers are accepted: {number}")

We're using Python's raise statement and passing in a TypeError exception object. We're using TypeError because the wrong type was given.

Also, if the number given is less than 2, we'll say that this isn't a valid value, so we'll raise a ValueError exception. The message for our ValueError exception will declare that only integers above 1 are accepted:

    if number < 2:
        raise ValueError(f"Only integers above 1 are accepted: {number}")

Here's a updated is_prime function with both of those conditions and raise statements:

def is_prime(number):
    if isinstance(number, float):
        raise TypeError(f"Only integers are accepted: {number}")
    if number < 2:
        raise ValueError(f"Only integers above 1 are accepted: {number}")
    for candidate in range(2, int(sqrt(number))+1):
        if number % candidate == 0:
            return False
    return True

In both of these cases, the error message we give to our exception object shows the number we were given, which will make our error message as helpful as possible when we're debugging our code.

Wow when we call the is_prime function with a floating point number, it raises a TypeError exception which says only integers are accepted:

>>> is_prime(4.5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/trey/is_prime2.py", line 6, in is_prime
    raise TypeError(f"Only integers are accepted: {number}")
TypeError: Only integers are accepted: 4.5

Similarly, if we call is_prime with 1 or 0, it raises a ValueError exception:

>>> is_prime(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/trey/is_prime2.py", line 8, in is_prime
    raise ValueError(f"Only integers above 1 are accepted: {number}")
ValueError: Only integers above 1 are accepted: 1

In both cases, the traceback that Python prints out shows the friendly error message we gave our exception objects.

Python has built-in exceptions

Where did TypeError and ValueError come from? We didn't define those classes, so they must be defined in Python.

If you look at help on the builtins module in Python, or if you look at the documentation for exceptions, you'll see the exception hierarchy:

>>> import builtins
>>> help(builtins)
Help on built-in module builtins:

NAME
    builtins - Built-in functions, exceptions, and other objects.

DESCRIPTION
    Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.

CLASSES
    object
        BaseException
            Exception
                ArithmeticError
                    FloatingPointError
                    OverflowError
                    ZeroDivisionError
                AssertionError
                AttributeError
                BufferError
                EOFError
                ImportError
                    ModuleNotFoundError
                LookupError
                    IndexError
                    KeyError
                MemoryError
                NameError
                    UnboundLocalError
                OSError
                    BlockingIOError
                    ChildProcessError
                    ConnectionError
                        BrokenPipeError
                        ConnectionAbortedError
                        ConnectionRefusedError
                        ConnectionResetError
                    FileExistsError
                    FileNotFoundError
                    InterruptedError
                    IsADirectoryError
                    NotADirectoryError
                    PermissionError
                    ProcessLookupError
                    TimeoutError
                ReferenceError
                RuntimeError
                    NotImplementedError
                    RecursionError
                StopAsyncIteration
                StopIteration
                SyntaxError
                    IndentationError
                        TabError
                SystemError
                TypeError
                ValueError
                    UnicodeError
                        UnicodeDecodeError
                        UnicodeEncodeError
                        UnicodeTranslateError
                Warning
                    BytesWarning
                    DeprecationWarning
                    EncodingWarning
                    FutureWarning
                    ImportWarning
                    PendingDeprecationWarning
                    ResourceWarning
                    RuntimeWarning
                    SyntaxWarning
                    UnicodeWarning
                    UserWarning
            GeneratorExit
            KeyboardInterrupt
            SystemExit

TypeError and ValueError are just two of the many built-in exceptions in Python. There are dozens of exceptions built into Python. We don't have to import anything in order to use these exceptions; they're just built-in.

We can define our own custom exception types by inheriting from another exception class, but it's a little bit unusual to do so. Unless you really need to distinguish your exceptions from exceptions that are built into Python, you probably won't create custom exceptions often.

The most common exception type I raise in my code is a ValueError exception.

Summary

If you have a specific condition in your function that should loudly crash your program (if/when that condition is met) you can raise an exception by using the raise statement and providing an exception object to raise.

You can make an exception object by calling an exception class, passing in a helpful error message.

Series: Exceptions

Exceptions happens! When exceptions happen, how should interpret the traceback for an exception? And how, when, and where should you catch exceptions?

To track your progress on this Python Morsels topic trail, sign in or sign up.

0%
Concepts Beyond Intro to Python

Intro to Python courses often skip over some fundamental Python concepts.

Sign up below and I'll share ideas new Pythonistas often overlook.