PEP 679 -- Allow parentheses in assert statements

How about making assert a function, just like what happened with print and enable it with a from __future__ import assert_function

That’s hard backwards incompatible because it means that the statement version won’t work (because it will be s function).

And I don’t think this change is important enough to justify a whole syntax backwards incompatible change behind a future import.

2 Likes

Given that people will likely need more time to actually see this warning, perhaps it’s a better idea to postpone the change to 3.12.

FWIW, I’m -0 on the idea: I don’t think

assert (...long 
        expression...
       ), (
        ...long error
        message...
       )

is too cumbersome to write and this already works. With the PEP, you avoid having to write two extra parens, that’s all:

assert (...long 
        expression...
       ,
        ...long error
        message...
       )

Please, add “throw a SyntaxError” on assert (a, b)” as rejentem ideas

I find the arguments from library implementers POV unconvincing. Library authors are generally painfully aware of which python version supports which features, and as a rule, will test against several python versions (or all supported python versions for medium-sized or larger projects).

As such, most affected projects will already see the syntax warning once they start supporting 3.10 (if they care to bother about test suite warnings), and I think most would appreciate to find out that their tests hadn’t been performing as advertised, and promptly refactor their tests to do the right thing.

Finally, it’s also easy to refactor into a version that supports 3.7-3.11 by creating temporaries for the condition & the message so that the statement version can be used. I think that in many cases it could end up looking like this.

-assert (really_long_boolean_condition_that_needs_to_be_checked,
-        "even_longer_message_that_should_be_shown_when_assertion_fails")
+cond = really_long_boolean_condition_that_needs_to_be_checked
+msg = "even_longer_message_that_should_be_shown_when_assertion_fails"
+if PY311:
+    # assert-with-braces only supported for python 3.11+
+    assert(cond, msg)
+else:
+    assert cond, msg

which would already prepares for an easy clean-up once 3.11 becomes the lowest-supported version.

They’d be more unconvincing if this wasn’t a silent failure: a test that should fail will pass silently. That’s very scary.
With assert, you’re aleady asking to get an error if something is wrong.

Let me update my suggestion from earlier:

  • Turn the existing warning into SyntaxError now.
  • For multi-line asserts, paretheses around individual parts remain the preferred style.
  • Also (eventually) add a with keyword, so 5-10 years from now we can write code that’s clearer to non-experts. Whichever way this goes, the comma doesn’t look surprising but it is, especially if (but not only) you don’t already know what it does. A keyword strongly hints that you should pay attention or look it up.

Yeah, but we’re talking about currently existing code out there that might be doing this, right? Irrespective of what CPython chooses to do, such “very scary” things probably do exist right now. I’d say that library authors will be thankful to know if they have silently passing tests that should actually fail. So whether through warnings, errors or new behaviour for 3.11 that surfaces that the old code was wrong, it’s a win for getting rid of those scary things.

2 Likes

Note that assert statements are completely ignored if you run Python in -O or -OO mode and that’s on purpose, since asserts are meant to test assumptions during development. Putting the condition into a separate variable will have the code still incur the cost of the check.

Now, if you want to have the test be part of the regular production code as well – which often is a better choice – you don’t need assert at all and can use a regular raise instead, which provides a lot more flexibility than assert, both in terms of syntax and features.

1 Like

Thanks for pointing that out - I’m aware of that, and clearly there are several considerations that will inform a given library author who finds out that their assertion is always passing spuriously about how best to fix it. I was commenting from the POV of assert in a test suite, not runtime-assert (where I fully agree raise would be better).

Thanks @encukou for the suggestion! I am afraid that we disagree on somw points on how to approach this. I will try to explain my thinking regarding this:

  • I don’t want to turn the existing warning into s Syntax Error because it will be very inconsistent. Why assert (X,Y) is a syntax error but no assert [X,Y] or even assert {1:2}? All have the same problem: the assertion is always true. The only difference with the first one is that it confuses people that think they they are grouping but for people that see the tuple then is very confusing why is that illegal. I don’t feel comfortable making that a syntax error but not the rest. Not only because I think is very inelegant bit also because syntactically speaking is very hacky and more difficult to maintain than accepting it.

  • As I mentioned, I really really really don’t want new syntax for something like this problem. I don’t want to introduce something as heavy as a keyword, future imports or something like that. This is not because I think the parentheses are the best way and I am being stubborn, but because I don’t think this problem can justify extra syntax or changing the language to they level. As I mentioned, the syntax cost is higher on every release and IMHO this problem doesn’t cut it.

If you or other community members think that the proposed approach doesn’t work and something more heavy (like new keyword or hard errors) will be required, and they is the general consensus, then I will understand if the PEP gets rejected, and that is ok. But I pourposedly want to keep the approach lightweight and simple because I believe that we should not approach every problem the language has with new syntax.

I will incorporate these thoughts on the pep “rejected ideas” section.

4 Likes

I understand, and I see I’ll need to explain my reasoning better as well. I’ll write a competing PEP :‍)

The PEP’s goal is not rewriting existing working code, but about making existing broken code Just Work. Specifically, if such assert ever fails, the authors will discover a bug that would otherwise remain hidden.

Sounds great :+1: but I see several benefits to keeping the existing SyntaxWarning (for now):

  1. If one doesn’t realize the assert only failed after upgrade to 3.11, they will discover the logic bug but might keep the assert bug in place — assert (cond, message) for will remain ineffective on <3.10.
    This could happen if they only test 3.11+ but same code is useful on older versions, or if they skip 3.10 that has SyntaxWarning e.g. only test 3.9 and 3.11.

  2. SyntaxWarning educates users even when the intended assert passes!

  3. It’s too early to encourage the new syntax. Here Marc had a good point — the burden of using the old syntax correctly is so low that as long as at least some of the time we write code for <3.11, IMHO it’s better to just learn the correct old syntax.

    In other words, the PEP cares about direct upgrade path — keep code as-is, it Just Works in future. :question::fast_forward::white_check_mark:
    IMHO the deprecate path is more pragmatic — force users to stop using this syntax, only later consider making it work. :question::stop_button::fire:_ :white_check_mark:

The only argument I see in the PEP why it’s better than status quo is:

(assuming that these cases still exist because users are ignoring syntax warnings)

So, looks like it hinges on balance between “you can miss it if you ignore warnings” and “you can miss it if assertion passes”.

  • What about upgrading it to SyntaxError? Too early?

  • What if we did both?! Treat assert (cond, message) with new semantics, actually executing assert — but also emit SyntaxWarning?

4 Likes

I like this as a compromise. The rest of Beni’s analysis is spot on - silently fixing this is a bit risky, but fixing it and warning that they’re now relying on recently changed behaviour seems like a good balance. The developer can always just switch to a fully compatible syntax if it matters.

I agree, if other people agree I am happy to update the PEP.

3 Likes

I like this too. It will help with the transition to the improved syntax. So then the idea is to remove the SyntaxWarning once all the Python < 3.11 are retired?

Works for me!

I have more thoughts than I can fit in a comment here, so I wrote a draft that does the opposite – try to steer toward a better language: Pre-PEP: Assert-with: Dedicated syntax for assertion messages

I prefer @encukou’s syntax, perhaps with else as @barry suggested there.

But if @pablogsal’s PEP is accepted I’d like to request that PEP8 be updated to require a single space after assert.

Syntax highlighting helps, of course, but if you’re not paying too much attention then

assert(expr, message)

looks a lot like

function(expr, message)

but behaves very differently in that message is only evaluated if expr is falsey and neither is evaluated with python -O. I believe PEP8 mandates no space before ( for a function call so this would mandate a distinction in style.

Putting it in PEP8 has no direct effect, of course, but I imagine it becoming enforced by pycodestyle and black and PEP8 zealots and so on.

The change only transforms assertions that pass in old versions into assertions that fail in new versions, never the other way around.

I think that’s a problem though and I think there are security implications. There could be code out there similar to the following:

x = False
try:
    assert (x, 'This always passes')
except:
    print('explode bombs')
else:
    print('all is fine')

The issue is mitigated by the fact that you currently get a SytnaxWarning for that code. However, I feel like we need to change it to a SyntaxError for a couple of releases before changing the meaning of that code. It’s annoying to have to wait that long to make the syntax change but that’s the safe way to do it.

I would like it if we could use parentheses to split long assert statements over multiple lines. It would be more consistent with the rest of the language.

This is preposterous. The condition should not be evaluated at all if -O is given. If you want code that works with all Python versions you can write

assert long_condition, \
    long_message

or you can write

assert (
    long_condition
), (
    long_message
)

There is no need for temporary variables or for a Python version check.

(And don’t tell me “PEP 8 discourages backslashes”. It also says “When in doubt, you should use your best judgment.”)

2 Likes