PEP 679 -- Allow parentheses in assert statements

10 Likes

Thanks for writing this up!
Did you consider a keyword, like assert a == b with "oops that shouldn't happen!" ? That could take care of the backcompat issue, and mirror what Python 3 did with except.

Thanks for the suggestion, @encukou !

The objective of the PEP is mainly to correct this specific situation with the minimum amount of changes. The idea is fundamentally to ensure that something that users expect to work will work directly, as opposed to introduce new syntax to cover something that was not possible before or that was too cumbersome and new syntax will make it easier. In particular, I will like to keep the change syntactically speaking to a minimum.

I will add a “rejected ideas section to the document with these thoughts”.

3 Likes

I’m a bit worried about the backward incompatibility. Not so much that currently no-op assertions suddenly start to get evaluated (as mentioned in the PEP), but that the new syntax gets used in code that wants to support older Python versions and end up with assertions that do nothing when testing on those orders versions.

Although that’s not that much worse than the status quo, and it something that can be warned about by linters (as they now do) or even fixed by a code formatter.

2 Likes

New syntax that’s a no-op on older versions is about the best case scenario :slight_smile: Normally you have to do conditional imports.

This will definitely remain as a gotcha for a few more years, but this change will eventually fix it properly.

7 Likes

That’s a valid concern. It will take many years to retire the old behavior. If this syntax starts to do the right thing in 3.11, the temptation will be to adopt it for code bases that still have to work on older Pythons. Just as today people may not be aware that their assert statements aren’t doing what they expect, once 3.11 does do what they expect, will they also realize that it only works that way in 3.11?

@encukou suggestion (or something like it) might be a better experience because at least that syntax isn’t valid in older Python’s, so rather than silently behaving differently, it will be obvious.

I’m not sure what problem @encukou’s suggestion would solve, since it’s just equivalent to assert expr, message.

An alternative could be to make assert where the test is a literal tuple a SyntaxError (because it’s never useful). This would also break backward compatibility, but remove the gotcha and avoid changing the meaning of code that is accepted by the parser.

But for that case we already have the SyntaxWarning that already is a SyntaxError if you use -Werror:

$ python3.9 -We
Python 3.9.7 (default, Nov  1 2021, 12:21:59)
[GCC 9.3.1 20200408 (Red Hat 9.3.1-2) - DPKG ID: 3.9.7-1+b20211227T19451525] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 1; assert (x, "msg")
  File "<stdin>", line 1
SyntaxError: assertion is always true, perhaps remove parentheses?

Making this directly a SyntaxError will be very weird because assert [1,2] will be valid but assert (1,2) will not, which is very inconsistent syntactically.

2 Likes

I appreciate the suggestion, but I really really really don’t want new syntax for something as small. The syntax price is bigger and bigger every day as we add more things and I really think this gotcha shouldn’t be fixed with extra new syntax (like new keywords or new semantic constructs).

I think that the key aspect of this PEP is to make what a lot of users expect to work just work, not to offer a new better way that’s superior to the old one.

7 Likes

That’s fair enough, but does this concern you?

It does concern me (and I am taking it into account) but I am not worried because:

  • The code is not syntactically invalid in older versions.
  • The code doesn’t not need a conditional import or a future import.
  • The code won’t do anything opposite on older versions, it will be a no-op (which is suboptimal but is not doing something completely different).

Also, the idea of the PEP is not to encourage the new version over the old one, is just to make it work.

Isn’t that undermining your proposal though? The meaning of assert [1, 2] will be entirely different from assert (1, 2).

Personally I don’t think that’s a problem (I am +1 on your PEP) but I don’t think bringing up assert [1, 2] is helping. :slight_smile:

I think the overriding reason to accept the PEP (as-is) is the fact that you can add parentheses to various other constructs to make them span multiple lines, like import, with and expressions in if or assignments (etc.) – in fact we know that users try doing this for assert as well, otherwise we wouldn’t have introduced that SyntaxWarning years ago.

It depends on how you look at it (as grouping or as a tuple) because you could also say that:

from x import [a,b,c]

should maybe work the same as del [x,y,z]. I assume the way I am looking at this is that overwhelmly more people think of the parentheses as grouping as not as a tuple and from that it follows that most people won’t be surprised of that fact.

But, technically, the concern applies and if you look at it purely from the syntactical point of view it is indeed a downside :slight_smile:

but I don’t think bringing up assert [1, 2] is helping. :slight_smile:

I agree. It was mainly an argument on why the proposal to solve this problem by making assert (x,y) a SyntaxError but not assert [x,y] would be very weird.

Sorry Pablo, I don’t understand your dismissal here.

I agree that looking forward to 3.11 and beyond, this would be an

excellent improvement. But if assert (cond, msg) becomes legal in

3.11, then there will be temptation for people to use that form in

library code that has to support older versions, where the parentheses

change the meaning of the code.

You say that “The code is not syntactically invalid in older versions”

but that is incorrect, assert (cond, msg) is valid in at least some

older versions, it’s just wrong. Example:

python3.7 -c "assert (False, 'This should fail')"

should fail, but silently succeeds. And there is no SyntaxWarning.

(There is a SyntaxWarning under 3.10.)

You say the code “won’t do anything opposite on older versions”, but it

does: it takes failing assertions and turns them into passing assertions.

Even though they shouldn’t do this, many people use assertions as a lazy

way of writing error checking code, so this could potentially take

libraries that work (or at least work if you don’t run them under -O)

and disable their error checking.

So I think that you need a stronger justification why this backwards-

incompatible change is safe to go ahead without a future import.

By the way, if this PEP is approved, I assume that we’ll still need to

keep the SyntaxWarning (and upgrade it to a SyntaxError?) to cover the

1-tuple and 3+ tuple cases

assert ()       # OK, this is like assert False

assert (cond,)  # always succeeds, needs a warning

Thanks Steven for your comment.

One small clarification in case my previous comments were not expressed correctly: I am not dismissing any concerns. I do value s lot those concerns and I am certainly taking them into account. I am just expressing my view on some of those aspects, which in particular shows that I am not too worried about some of the problems raised, but I do admit that they are valid concerns and I want to listen and understand them, but at the end of the day is ok if we have different views on how critical those things are.

You are correct here, but my argument is that if this is done in libraries then the library authors are as responsable of choosing what semantic constructs they can as with any other new Python syntax. Library authors must know today that using the walrus operator has consequences and this is also true here with this change.

Notice that the important key here is that in older version, a syntax warning is already emitted so if library authors check with older versions, the problem won’t lass absolutely unnoticed. They will know that the new feature is not working in those older versions and then they can take an informed decision.

I’m afraid I don’t understand what you mean. My message says that the parenthesized version is not syntactically invalid in older version and that is true: older versions can parse the expression (therefore is syntactically valid). The fact that is evaluated differently as you point out is a different problem, but they point I made is just noting that old expressions can parse correctly the expression.

Compare this with the new parenthesized context managers. Old versions cannot parse those so they become syntax errors in old versions.

You are absolutely right here. In my message I was mainly referring as transforming passing assertions into noop assertions which is milder, but is true that for the case of failing assertions the change is more dramatic so my point is indeed weaker.

I understand your point but unfortunately there isn’t much extra justification for the backwards incompatibility here other than the fact that the new version in old code already emits a warning (in 3.10 by default) and in older versions of you use -We and that linters are already flagging code like this constantly.

If the community thinks this is not enough, then I am happy to accept that as Is a perfectly valid outcome based on a valid concern. In my case, I think the benefits for the future justify the problems here with backwards compatibility but that’s just my opinion and the community (or the steering council without myself) must decide if the benefits outweigh the problems/risk or not.

4 Likes

I’m +1 on the new syntax. It’s similar to a number of other cases where we’ve allowed parentheses to let people extend a construct over multiple lines. The fact that the new proposal changes the meaning of a currently valid but useless case doesn’t bother me (especially as it already emits a SyntaxWarning).

4 Likes

People keep saying that changing this behaviour is okay because it will
emit a SyntaxWarning. Have you tried it?

[steve ~]$ python3.7 -c "assert (False, 'msg')"
[steve ~]$ 

No SyntaxWarning.

I’m in favour of the long-term change to allow parens here, but not if
it means that code that was working (that is, assertions were correctly
failing) will break (assertions incorrectly passing).

Turning assertions that fail into assertions that pass is a breaking
change. This is an especially insidious breaking change because it risks
retroactively breaking people’s code, if they update it to 3.11 style
assertions with parentheses but fail to test it sufficiently in 3.7
onwards.

The obvious fix for this is to require a future import.

Have I missed something? Is there a hole in my reasoning?

@pablogsal, it may be worth it to explicitly note in the PEP that the SyntaxWarning was enabled by default in Python 3.10.

FWIW, I’m +1 on this PEP.

1 Like

IIUC, this is not the case. Quoting the Backwards Compatibility section of PEP 679:

The change is not technically backwards compatible, as parsing assert (x,y) is currently interpreted as an assert statement with a 2-tuple as the subject, while after this change it will be interpreted as assert x,y.

On the other hand, assert statements of this kind always pass, so they are effectively not doing anything in user code. The authors of this document think that this backwards incompatibility nature is beneficial, as it will highlight these cases in user code while before they will have passed unnoticed (assuming that these cases still exist because users are ignoring syntax warnings).

2 Likes

Steven, you are reading this backwards. The change only transforms assertions that pass in old versions into assertions that fail in new versions, never the other way around.

2 Likes