Pre-PEP: Assert-with: Dedicated syntax for assertion messages

Hello,
Here is my counter-proposal to Pablo’s PEP 679. Rather than doing the smallest change to fix a problem, it tries to steer towards a better future (in a few years/decades it’ll take for new syntax to be usable).
It’s not as polished as PEP 679, but the idea should be clear.

I’ve had "suggest assert-with" sitting on my “maybe-later” list for a while now, thanks to Pablo & co. for making me write it up!


PEP: XXX
Title: Assert-with: Dedicated syntax for assertion messages
Author: Petr Viktorin encukou@gmail.com
Discussions-To: (XXX: Discourse thread)
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 18-Jan-2022
Python-Version: 3.11
Post-History:

Abstract

This PEP proposes allowing to use the with keyword to introduce the
exception message in assert statements.

The current syntax, a comma, will still be valid, but discouraged
in CPython’s style guide (pep:8).

Using a tuple for the assertion expression will raise a SyntaxError,
rather than a warning as in Python 3.10.

Motivation

The most common usage of the coomma in Python is as a separator of
variable-length lists of homogeneous elements, like the the items of a
tuple display, parameters/sarguments of functions, or import targets.

Not using the comma in this way leads to confusion, especially in places
where the comma looks like it could separate tuple elements.

Prior art: Except-as

In Python 2, except exc_type, name had the same
meaning as today’s except exc_type as name.
The comma separated two different kinds of things: a type to be
checked and a name to be assigned to.
This usage was deemed confusing, and in :pep:3110, the comma was replaced
by the as keyword:
as an alternative in Python 2, and as the only option in Python 3.

Assert-with

The remaining use of a comma to separate heterogeneous stuff is
the `assertstatement, where the comma separates the expression to be tested and the optional exception message. This usage is confusing as well, for many of the same reasons as inexcept``.

See the Motivation in :pep:679 for details.
(That Motivation could essentially be copied here.
Pablo explains the practical issue very well.)

Rationale

A keyword expresses the meaning of the two-clause assert statement
better than the comma.

The comma will continue to be valid, so old code will continue to work,
but we expect code style guides and linters to discourage it, making it
rare in new code that targets Python 3.11+.

Specification

The with keyword will be allowed in place of the comma between the
condition and message of the assert statement, making the following
statements equivalent::

assert 1 + 1 == 3 with "Expected wrong result!"
assert 1 + 1 == 3, "Expected wrong result!"

Using a tuple as the assertion expression,
which causes a SyntaxWarning in Python 3.10, will instead raise a SyntaxError::

assert (1 + 1 == 3, "Expected wrong result!")  # SyntaxError

Backwards Compatibility

A SyntaxWarning is turned into a SyntaxError, so programs that contain this
kind of ineffective assert statement will fail to compile.
This is a large downside of this proposal, but is mitigated by several factors:

  • The only use case of an assert with an always-true assertion is testing,
    exploring or emulating the Python language.
    It is OK to break these uses if the change is beneficial enough.
  • Python 3.10 already emits a warning in this case. There will be a
    deprecation period, albeit shorter than the two releases specified in
    :pep:387.
  • The fix is easy (unless the affected source code cannot be modified).

Security Implications

No negative security implications are known.

How to Teach This

If Python 3.10 an below can be ignored, teach the assert statement with
with instead of the comma.

Otherwise, teach the comma as before.

If an IDE, linter or code style checker can positively determine that
some code doesn’t support Python 3.10, it can choose to suggest
replacing relevant commas with with.

Reference Implementation

None yet

Rejected Ideas

Allow parentheses in assert statements (PEP 679)

Allowing parentheses around the entire body of the assert statement
is perhaps the easiest way to solve the issue of misleading code,
but it does not address the “overloaded comma” between heterogeneous
syntax elements.
A keyword expresses the meaning of the code better.

PEP 697 has a forward compatibility issue. Code like::

assert (1 + 1 == 3, "Expected wrong result!")

would raise AertionError under Python 3.11 but silently do nothing in
earlier versions.
Since assert is frequently used for testing (e.g. with the
Pytest framework), this means tests for older Python versions would
become unreliable.

Disallow the comma

With parentheses around the assertion expression disallowed,
code using the old syntax will not be dangerously misleading::

assert 1 + 1 == 3, "Expected wrong result!"  # works as before

Discouraging the comma in new code, rather than disallowing it entirely,
means old, battle-tested code will continue to work without modifications.

Disallow square brackets around the expression

Code like the following will still be valid::

assert [1 + 1 == 3, "Expected wrong result!"]  # does nothing - always True

Like the variant with parentheses, this assertion currently always fails,
yet it will not be turned into a SyntaxError.
This is inconsistent with the treatment of parentheses::

assert (1 + 1 == 3, "Expected wrong result!")  # AssertionError

Disallowing parentheses is not a general design principle,
but a special case in atonement for (what is in retrospect) a past design mistake.
Other always-True assertions should not be turned into errors.
Catching them should continue to be be the job of linters, not the parser.

Using a different keyword

with is an existing keyword and fits the use well enough.
There’s no need to invent a new keyword, soft or hard.

Open Issues

1

PEP 679 might be updated with @cben’s suggestion:

Treat assert (cond, message) with new semantics, actually executing assert — but also emit SyntaxWarning?

This idea could work with with as well, and might be a better choice than
SyntaxError.

2

The grammar changes are not implemented.
They seem feasible, but that’s an educated guess at this point.

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.


Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8
End:

1 Like

Thanks for putting your thoughts into a pre-PEP @encukou . While I understand that this is a purely-syntactic response to @pablogsal 's PEP, we of course need to still improve the semantics of assert and -O. But that’s for another PEP.

As much as I hate to bikeshed, I’m a’gonna throw this out there. :smile:

Is with the right keyword to use here? Think about the English phrase, “I assert my support with enthusiasm!” The “enthusiasm” is bound to a positive assertion, not a negative assertion. To translate this into Python, it says to me that the message is associated with a truthy assertion, not a falsey assertion. That’s exactly the opposite semantics of what the assert statement is promoting.

Instead, it should be an opposing linkage, i.e. asserting the truthiness of the expression, and if false, then an AssertionError with the message is raised.

Grammatically, I think you would be able to define a soft keyword here, but maybe else serves the purpose just fine:

assert 1 + 1 == 3 else "Expected wrong result!"

I thought about except as a way to evoke catching AssertionErrors, and raise (or even raises) as evoking raise AssertionError, or even else raise, but none of those read well to my eyes for similar reasons given above.

11 Likes

Maybe an as coloured bikeshed?

assert x == 2 as "x was supposed to be an even and prime number"

I guess it depends on whether you phrase your message as a detailed description of the condition you expected, or as an explanation of what must have actually happened.

(Edit: since I’m here now, -1 on changing the syntax, just because I would rather not change syntax if it doesn’t have to be changed. I don’t believe Pablo’s proposal constitutes a syntax change.)

2 Likes

What would a multi-line assert statement look like with the with syntax?

as makes me think the assertion (exception?) gets bound to the RHS, like imports, exceptions and context managers

4 Likes

As a Python user, I like the idea of being more explicit here like with except, but would find with confusing (especially so if I was a less experienced user) due to its overloaded meaning as well as the reasons mentioned by @barry . I initially thought "why not as" like @steve.dower , but I’d find it even more confusing for the reasons @EpicWink mentioned. Barry’s suggestion of else seems to fit the bill much better, since the semantics are clear just from reading the line in English, and it is much closer to the use of else…well, else-where (okay, I’ll go now).

2 Likes
assert (
    long_condition
) else (
    long_message
)

Guess it’s else syntax now! I was a bit afraid that bikeshedding would drown other feedback, but if there’s not much other feedback, it’s great. Thanks!

I was a bit afraid to suggest “Black-style” closing parens on their own line. PEP 8 doesn’t explicitly forbid them but doesn’t use them in any examples. I like them, but then I also like to write } else { in C, which is explicitly against PEP 7. Bit it seems Guido’s warmed up to this style. If I make this a PEP, it’ll include this example.

I think the next step, both here and in Pablo’s PEP 679, is to integrate (or reject) the @cben's idea, which is not as straightforward as it looks.
Unfortunately I don’t have much time to drive this forward. Co-authors welcome. Otherwise I’ll probably wait for Pablo and copy his solution. (I mean no conflict, the pre-PEP is here to explain my reasoning so we can choose the the best idea.)

1 Like