Thanks to visit codestin.com
Credit goes to www.pythonmorsels.com

Python's assert statement PREMIUM

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
5 min. read 4 min. video Python 3.10—3.14
Tags
Python Morsels
Watch as video
04:06

Let's talk about Python's assert statement.

assert either does nothing or raises AssertionError

When the assert statement is given a value that's True, it does nothing:

>>> is_valid = True
>>> assert is_valid

But if assert is given False, it raises an AssertionError exception:

>>> is_valid = False
>>> assert is_valid
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Assertions check the truthiness of the given value

The assert statement doesn't actually check for True or False directly, though; it checks the truthiness of the given value.

So an empty list would raise an AssertionError because empty lists are falsey:

>>> matches = []
>>> assert matches
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

But a non-empty list would do nothing, because non-empty lists are truthy:

>>> matches = ["145", "578"]
>>> assert matches

Add error messages to assert statements

When an AssertionError is raised, it's sometimes unclear what went wrong.

>>> x = 7
>>> y = 10
>>> assert x == y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

When Python runs an assert statement, it first evaluates the given expression and then it checks the truthiness of that value, and that's it. The assert statement doesn't know anything about the meaning of the expression we've given to it.

By default, AssertionError exceptions don't have an error message associated with them. But the assert statement also works with an optional second expression that will represent the error message that should be shown if an AssertionError was raised:

>>> assert x == y, f"{x} != {y}"

When we run that assert statement, we'll see the message for our AssertionError exception says 7 does not equal 10, which is much more helpful than just seeing AssertionError:

>>> assert x == y, f"{x} != {y}"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: 7 != 10

Keep in mind, though, that assert is not a function; it's a statement. So if we were to put parentheses around these two expressions, we'll have a potential bug in our code:

>>> assert (x == y, f"{x} != {y}")
<stdin>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?

We're giving the assert statement a two-item tuple, and two-item tuples are always truthy. Python 3.10 and above will show a SyntaxWarning for such problematic assertions.

Assertions are used in automated tests

The assert statement is very commonly used in automated tests:

def has_duplicates(items):
    return len(set(items)) < len(items)


# Automated tests

def test_empty_list():
    assert not has_duplicates([])


def test_no_duplicates():
    assert not has_duplicates([1, 2, 3])


def test_two_duplicates():
    assert has_duplicates([1, 2, 3, 2, 3])

Specifically, it's commonly used along with the third-party pytest library:

(dups) ~ $ pytest has_duplicates.py
=================== test session starts ===================
platform linux -- Python 3.11.0, pytest-7.4.0, pluggy-1.2.0
rootdir: /home/trey
collected 3 items
has_duplicates.py ...             [100%]
==================== 3 passed in 0.01s ====================

Using assert outside of automated tests

While assert is most commonly seen in automated tests, you may occasionally see assert used outside of tests as well.

For example, the third-party rich library uses assert to validate certain conditions which might cause odd errors if they were left unvalidated. These are conditions that might not warrant their own exception, because they're somewhat uncommon. But if they were left unchecked, odd bugs might crop up, and seeing an AssertionError is better than seeing unusually buggy behavior.

For example, this pad method from rich's Text class uses an assert statement to check that the character argument that's given to this method represents exactly one character:

class Text(JupyterMixin):

    ...

    def pad(self, count: int, character: str = " ") -> None:
        """Pad left and right with a given number of characters.

        Args:
            count (int): Width of padding.
        """
        assert len(character) == 1, "Character must be a string of length 1"
        if count:
            pad_characters = character * count
            self.plain = f"{pad_characters}{self.plain}{pad_characters}"
            _Span = Span
            self._spans[:] = [
                _Span(start + count, end + count, style)
                for start, end, style in self._spans
            ]

That assertion ensures that character is not a multi-character string, because if it was, that might cause odd silent bugs in this code.

Also, this expand_tabs method uses an assertion to make sure that the tab_size attribute on our Text object is not None before it continues onward using that tab_size value:

class Text(JupyterMixin):

    ...

    def expand_tabs(self, tab_size: Optional[int] = None) -> None:
        """Converts tabs to spaces.

        Args:
            tab_size (int, optional): Size of tabs. Defaults to 8.

        """
        if "\t" not in self.plain:
            return
        pos = 0
        if tab_size is None:
            tab_size = self.tab_size
        assert tab_size is not None
        result = self.blank_copy()
        append = result.append

        ...

Assertions can be disabled

Now, you may sometimes hear Python developers discourage the use of assert because assert statements can be disabled in Python.

If you run Python with the optimize flag, which is -O, Python will set the value of the __debug__ variable to False (it defaults to True):

$ python3 -O
Python 3.11.0 (main, Oct 25 2022, 11:38:08) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> __debug__
False

And when -O is set, Python will also ignore any assertion statements in our code. So these assertions do nothing now:

>>> assert False
>>> assert True

Avoid using assertions for user-facing errors

So if your code relies on assert to check something that's actually important for your code to function, make sure your code isn't ever run with the optimize flag enabled, or those assertions won't be run!

Here's an example of code that relies on an assertion to check something important:

from urllib.request import urlopen


def fetch_name():
    with urlopen("https://pseudorandom.name") as response:
        assert response.status == 200  # Fail on HTTP error status
        return response.read().decode().strip()


if __name__ == "__main__":
    print(fetch_name())

So Python developers sometimes use assert to clarify assumptions in their code:

def calculate_discount(price, discount):
    assert 0 <= discount <= 100, "Discount should be between 0 and 100"
    return price * (1 - discount/100)

But if an AssertionError might reasonably happen for an end-user, or in a situation where catching an exception might actually be desirable, I would recommend explicitly raising an exception instead:

def calculate_discount(price, discount):
    if not (0 <= discount <= 100):
        raise ValueError("Discount should be between 0 and 100")
    return price * (1 - discount/100)

Instead of using assert, we could use an if statement to check whatever condition we're trying to check, and then raise an appropriate exception with a helpful error message.

Use assert for tests and for assumption checking

Python's assert statement is very handy in automated tests, and for validating assumptions in our code that might cause bugs if left unchecked.

Python Morsels
Watch as video
04:06
This is a free preview of a premium screencast. You have 2 previews remaining.