Python / Assert

Assert

By Marcelo Fernandes Oct 03, 2017

Using Python Assertions

Python assertion is a feature that isn't very popular, or maybe doesn't get the attention that it should get, therefore we should talk a little bit about it, and where it usage is best applied and where it isn't.


Asserts on a practical example:


def weekday_to_integer(weekday):
    mapper = dict(sun=1, mon=2, tue=3, wed=4, thu=5, fri=6, sat=7)
    assert weekday in mapper
    return mapper.get(weekday)

This is a pretty easy example, it grabs a string with a 3 character week day such as 'sun', and transforms it into an integer. An expected behaviour can be seen in the next snippet:


print(weekday_to_integer('sun')
# 1

Sometimes our functions return values that we didn't expect them to return, or they are called with someone that was unexpected as well. It might be because of different reasons: Maybe the person that is going to use your function in the future used 'SUN' as an input instead of 'sun', and it will return a None object instead of the expected integer 1.

So, what happens when an unexpected call is made and the assertion returns False instead of True?


print(weekday_to_integer('SUN')
--------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in weekday_to_integer
AssertionError

Therefore this assertion guarantees that no matter what happens, the int can only be returned if the input variable 'weekday' is a key that belongs to the mapper dictionary.

This behaviour shows us that Assertion is a very useful tool, specially if you need to debug your code and guarantee that your functions are only returning the expected values. The traceback returned from the assert is very useful, and even gives you the line where the code appeared.

Why would I use Assertion if I can use the Regular Exceptions?

One thing that python taught me over time, is that there is no duplicated content on the language implementation and standard library itself. If you think that there are two functions that solve the same problem, I advise on looking a little bit deeper, because you might find out a pretty cool feature hidden on one of those two functions. With assertions and exceptions call, this history repeats.


While exceptions in python are meant to be control-flows, i.e: They should be used to handle expected behaviours, such as returning an certain HTTP status code if the exception EmailSendingException was raised, assertions should be used to be internal debug checkers, i.e: They are used to inform developers about unrecoverable errors in a code.

Assertions should not act as signal triggers to expected error conditions. On the contrary, they should signal the unexpected errors and things that should not occur by any means.

Therefore, whenever an "impossible" condition occurs, the assertion will spot the mistake with a traceback to the line that raised the AssertionError, and the developer will easily keep track of what's buggy with his/hers software.


How does python treat assertions?

A closer look of what python does on runtime when an assertion is called, should look like the following:


if __debug__:
    if not expression1:
        raise AssertionError(expression2)

The assertion takes two expressions, the first one is the conditional to be evaluated, and the second one it he optional text message to be raised on the Traceback. But why is there a top conditional evoking __debug__ global variable? Well, as it was stated before, assertions are intended to be used for debuging, and this variable is True in most cases, but sometimes it isn't.

You should take care with __debug__ global variable, because it can be set to False and hence, all your assertions go to the bathtub hole. Whenever the environment variable PYTHONOPTIMIZE is set in CPython, the assertions debug global variable is set to False, and your interpreter won't care for your assertions anymore.


When I shouldn't use assertions?

You shouldn't use assertions to validate inputs or variables in general, as they can be disabled if the debug global variable is false. For example:


def erase_table(user, table):
    assert user.has_privileges()
    assert table not in PRODUCTION_TABLES
    database.get(table).delete()

You should NEVER use assertions in a function like this, because whenever the debug variable is not True, the user that is deleting the table does not need to have privileges (be an admin per instance), in order to delete the tables, and the tables themselves could be on usage in production. You can imagine the mess that it would cause, right? Therefore keep in mind that asserts should be used for debugging purposes only.


Notes