When to Use assert and raise in Python?

I recently had a discussion about the difference of Python’s assert keyword vs. raising “normal” exceptions. For quite a long time, I was uncertain when to use assert, too. In this article, I’ll present some examples and rules of thumb for when using assert or raising exceptions is – in my opinion – more appropriate.

When do I use assert?

You use assert to check and document invariants of your code. Invariants are conditions in the middle of your code that should always be true (For example: “At this point in time

# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
0 should always be a number between 0 and 10”).

Assertions can help others (including future-you) to understand your code and they should make your program crash if the assertion becomes false (because of a programming error).

Here are some examples for when assert is useful:

# Bad:

def func(a, b):
    x = [a, b]
    ...  # Some operations on "x"
    # x should still contain 2 elements:
    return *x


# Good:

def func(a, b):
    x = [a, b]
    ...  # Some operations on "x"
    assert len(x) == 2, f'len(x) == {len(x)}'
    return *x

# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()

# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0

Validation of input parameters

Sometimes, assert is used to validate input parameters for functions. You should avoid this because assert statements are not executed when Python is invoked with

# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
4. In this case, you would suddenly loose all input validation.

# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
5 also carry less semantics than “normal” exceptions – like
# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
6 when the user passed a string and not a number, or
# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
7 when the user passed a negative number when only positive numbers are allowed.

# Bad:

def sum(a, b):
    assert isinstance(a, int), 'a must be an int'
    assert a >= 0, 'a must be >= 0'
    ...

# Good:

def sum(a, b):
    if not isinstance(a, int):
        raise TypeError(f'a must be an int but is a {type(a)}')
    if a < 0:
        raise ValueError(f'a must be >= 0 but is {a}')
    ...

There are, however, exceptions possible for this rule. Imagine the following snippet where we want to add/delete a user from a group on an LDAP server:

def add_to_group(conn, user, group):
    _modify_group(conn, user, group, ldap3.MODIFY_ADD)

def delete_from_group(conn, user, group)
    _modify_group(conn, user, group, ldap3.MODIFY_DELETE)

def _modify_group(conn, user, group, op):
    assert op in {ldap3.MODIFY_ADD, ldap3.MODIFY_DELETE}
    conn.modify(group.dn, {'uniqueMember': [(op, [user.username])]})

In this case, it’s appropriate to use assert because

# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
9 should never be called by our users but only by
# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
0 and
# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
1.

What kind of exceptions should I raise?

Within you own code, the built-in exception types – especially

# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
7,
# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
3,
# We have widget like this:
#
#   Step size: ( ) auto   (•) manual: [___300] seconds

# Bad:

if self.button_auto.isChecked():
    return None
else:
    # The user provided a manual step size:
    return self.step_size_input.value()


# Good:

if self.button_auto.isChecked():
    return None
else:
    assert self.button_manual.isChecked()
    return self.step_size_input.value()
6 and
# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
5 – are usually sufficient.

Libraries often define their own exception hierarchy with their own base class. For example, Requests has

# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
6 from which all other exception types inherit.

This makes it a lot easier for users to catch your library’s exceptions. If you raise all kinds of exceptions (and then you don’t document that, too), your users will end up using a broad/bare excepts which is bad.

# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
7 is bad because it results in all kinds of nasty side effects. For example, it would also catch (and possibly ignore)
# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
8 (
# Bad:

def fill_list(self):
    # self.dest should be empty, but self.source not:
    while self.source_not_empty():
        self.dest.append(self.take_from_source())


# Good:

def fill_list(self):
    assert len(self.dest) == 0
    assert len(self.source) > 0
    while self.source_not_empty():
        self.dest.append(self.take_from_source())
    assert len(self.dest) > 0
9) or
# Bad:

def sum(a, b):
    assert isinstance(a, int), 'a must be an int'
    assert a >= 0, 'a must be >= 0'
    ...

# Good:

def sum(a, b):
    if not isinstance(a, int):
        raise TypeError(f'a must be an int but is a {type(a)}')
    if a < 0:
        raise ValueError(f'a must be >= 0 but is {a}')
    ...
0.

And that’s why you should also never raise

# Bad:

def sum(a, b):
    assert isinstance(a, int), 'a must be an int'
    assert a >= 0, 'a must be >= 0'
    ...

# Good:

def sum(a, b):
    if not isinstance(a, int):
        raise TypeError(f'a must be an int but is a {type(a)}')
    if a < 0:
        raise ValueError(f'a must be >= 0 but is {a}')
    ...
1.

TLDR

  • Use assert to detect programming errors and conditions that should never occur and which should crash your program immediately, e.g., invariants of an algorithm.

    Warning: Assertion checks are stripped and not executed if Python is invoked with the

    # Bad:
    
    def sum(a, b):
        assert isinstance(a, int), 'a must be an int'
        assert a >= 0, 'a must be >= 0'
        ...
    
    # Good:
    
    def sum(a, b):
        if not isinstance(a, int):
            raise TypeError(f'a must be an int but is a {type(a)}')
        if a < 0:
            raise ValueError(f'a must be >= 0 but is {a}')
        ...
    
    3 or
    # Bad:
    
    def sum(a, b):
        assert isinstance(a, int), 'a must be an int'
        assert a >= 0, 'a must be >= 0'
        ...
    
    # Good:
    
    def sum(a, b):
        if not isinstance(a, int):
            raise TypeError(f'a must be an int but is a {type(a)}')
        if a < 0:
            raise ValueError(f'a must be >= 0 but is {a}')
        ...
    
    4 option!

  • Raise an exception for errors that are caused by invalid user input or other problems with the environment (e.g., network errors or unreadable files).

    What is the difference between assert and raise AssertionError in Python?

    In Python, the assert statement is used to continue the execute if the given condition evaluates to True. If the assert condition evaluates to False, then it raises the AssertionError exception with the specified error message.

    Under what circumstances would you place an assert () into your code?

    You should only use assert to check for situations that "can't happen", e.g. that violate the invariants or postconditions of an algorithm, but probably not for input validation (certainly not in libraries). When detecting invalid input from clients, be friendly and return an error code.

    What is the difference between except and raise Python?

    raise allows you to throw an exception at any time. assert enables you to verify if a certain condition is met and throw an exception if it isn't. In the try clause, all statements are executed until an exception is encountered. except is used to catch and handle the exception(s) that are encountered in the try clause.

    What is the use of raise statement in Python?

    The raise keyword is used to raise an exception. You can define what kind of error to raise, and the text to print to the user.