69

In Python, assert is a statement, and not a function.

According to the docs, assert expression1, expression2 is equivalent to

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

The docs also say

The current code generator emits no code for an assert statement when optimization is requested at compile time.

Without knowing the details, it seems like a special case was required to make this possible. But then, a special case could also be used to optimize away calls to an assert() function.

If assert were a function, you could write:

assert(some_long_condition,
       "explanation")

But because assert is a statement, the tuple always evaluates to True, and you get

SyntaxWarning: assertion is always true, perhaps remove parentheses?

The correct way to write it on multiple lines is

assert some_long_condition, \
       "explanation"

Why this decision? Are there advantages to having assert as a statement (and a reserved word) instead of a function?

2
  • 9
    It's somewhat surprising that this wasn't converted to a function with print. Although, I prefer not to use raw assert statements. I only use unittest.TestCase.assertTrue(), and friends in test code and keep production code assertion-free. Commented Nov 15, 2012 at 1:51
  • 1
    I use to throw AssertionError when parameters are technically valid but some non-trivial assumption regarding them (and usually other parts of environment) does not stand. Provided there is no better category, of course. Commented Nov 15, 2012 at 2:17

5 Answers 5

43

Are there any advantages to having assert be a statement (and reserved word) instead of a function?

  1. Cannot be reassigned to a user function, meaning it can be effectively disabled at compile time as @mgilson pointed out.
  2. The evaluation of the second, optional parameter is deferred until if/when the assertion fails. Awkward to do that with functions and function arguments (would need to pass a lambda.) Not deferring the evaluation of the second parameter would introduce additional overhead.
Sign up to request clarification or add additional context in comments.

5 Comments

I wouldn't say "more easily disabled" since in the case where assert is a function, I think it would be impossible to optimize away :).
@mgilson you're right, darn elections just over, I must have misspoken. ;) But kidding aside yes, you're absolutely right.
I hadn't though of the second point you raised (get it? raised? since assertions raise AssertionError?). +1 for that.
Just want to add one more point, as mentioned in my another answer: it is not only the 2nd optional parameter is deferred. The first parameter is also deferred so that it will only be evaluated if it is in debug mode
Point 2. can also be considered a disadvantage. If you have some typo in the second parameter (example: forgot the f on an f-string), you wouldn't find out until the assertion actually fired, and by then it's too late to provide a useful context message.
20

One of the wonderful things about assert in python and in other languages (specifically C) is that you can remove them to optimize your code by just adding the correct #define (optionally on the commandline with any compiler I've ever used) or optimization flags (-O in python). If assert became a function, this feature would be impossible to add to python as you don't know until runtime whether you have the builtin assert function or user-defined function of the same name.


Also note that in python, function calls are reasonably expensive. Replacing inline with the code if __debug__: ... is probably a lot more efficient than doing a function call which could be significant if you put an assert statement in a performance critical routine.

4 Comments

I'm confused by your statement that "the (CPython) code generator doesn't remove assert statements (currently)". From the docs, it seems like it does remove the statements (emits no code for them) if you turn optimization on. Am I misunderstanding?
@cberzan -- You're correct. I didn't see your comment until just now, but it looks like python does remove them with the proper optimization flags turned on. I've edited to remove that error. (Thanks!)
This is a pretty unconvincing answer. Everything in Python is so expensive, that removing assert statements vs just nerfing an assert function would be a micro-optimization at best. I have also never in my career seen anyone use the -O flags or compile bytecode that way.
Don't get me wrong -- I've never used it either. But Python's been around for a LONG time and this is how assert works in so many other languages. These decisions were made in a different time -- possibly with a lot more optimism (or pessimism depending on how you look at it) around what performance of different things would be expected to look like. Changing something like this is a MUCH bigger lift than anyone ever expects (see the pain that we all experienced in the python2.x -> 3.x transition). I do think that the other answer's deferred evaluation is a compelling argument too.
9

In addition to the other answers (and sort of off-topic) a tip. To avoid the use of backslashes you can use implicit line joining inside parenthesis. ;-)

Instead of:

assert some_long_condition, \
       "explanation"

You could write:

assert some_long_condition, (
       "explanation")

Comments

7

I am no expert in Python, but I believe performance is one of the biggest reason.

if we have assert(expression, explanation) as function, if expression is expensive to evaluate, even we are in non-debug mode, Python needs to evaluate both expression to pass it to the assert function.

By expanding the assert, the expression and explanation statement is in fact not evaluated unless they are really needed (when debug evaluates to true). I believe it is critical if we want to make assert not affecting performance when not necessary (i.e. no performance hit in production system).

2 Comments

Why is it hard to make a function evaluate to a nop or something if debug evaluates to false?
@rasen58 I am not saying the function itself, but the EXPRESSION that we passed as parameter of assert. You cannot make them noop because Python is not lazy-evaluated. Of course you could delay the expression evaluation by changing it to a lambda and evaluate it in the function only if it is non-debug. However this is not the choice taken (I believe assert is available earlier than lambda, and using lambda here could be verbose)
0

Assertions can be disabled entirely at runtime. If assert were an ordinary function, that function could be bound to an arbitrary name at runtime, and it would be difficult (if not impossible) to determine statically which function call to eliminate without executing the code. You couldn't disable the call itself, only make it behave differently once it actually was called.

The assert statement, on the other hand, is entirely distinct from any function call, and cannot be modified or aliased at runtime, so it can be detected statically during parsing.

1 Comment

What does this add over the existing answers from 10+ years ago?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.