Thinking about this a bit more, here is a hypothetical transition plan:
Python 3.X (earliest is 3.13 at this point): add a deprecation message to assert statements when they raise[1], saying something like Warning: in Python 3.X+2 asserts will be stripped by default. Use "python -X dev" to evaluate them, or replace with "if not [condition]: raise" to evaluate in normal run modes[2]
If assertion errors are part of a package’s normal runtime behavior (e.g. they are used to check for conditions that the user is likely to break) then this message should be seen fairly often. This will generate the necessary PRs for those projects.
If a package’s assertions basically never go off over the course of two years, then they are likely to be true invariants and the developer is probably fine to strip them out of normal running.
Beyond the normal documentation/announcements, it would be good for linters to say something about this, to inform even more people of the coming change. I’m not sure how to make that work without generating a lot of noise, though.
In 3.X+2, change the default behavior to strip assert statements in the standard runtime mode (which is I guess “debug” mode but doesn’t really merit the name). The -O flag becomes a no-op.
even if the assertion is caught, or perhaps especially then ↩︎
you could also say if __debug__ and not [condition] to retain precisely the same behavior but I don’t think that’s needed ↩︎
It seemed like “this will generate a lot of silly issues/PRs” was a major concern for the other proposal. If it’s not true for this one, then it wouldn’t be true for that one, right?
Pedantically I would venture that this will generate the necessary PRs, and the ones it doesn’t generate weren’t necessary.
Maybe your main point is that these will be issues, and not PRs, which is fair enough. But the argument here is that those usages are an issue.
The main point is that tying the message to a raised AssertionError is most likely to put it in front of people who care and hide it from being doing the right thing who will be unaffected by this change.
My understanding (based on a note in the OP, which might be wrong!) was that pytest already modifies the AST to work under -O, so they would continue to work when the standard mode is -O
Not only pytest, it’s a common enough paradigm that other test
runners (testr is a popular alternative) follow the same patterns
for consistency and compatibility.
IMO, that’s a very optimistic assumption to make - I’d assume they detect -O and so do the modifications if it’s present, so it would not “continue to work”. But once again, you’re creating work[1] for currently perfectly fine code, and it’s not even to help warn people with bugs in their code that they need to fix it - it’s to let those people off with writing buggy code.
This simply isn’t reasonable.
If you can find a way of flagging the issue with -O to people who currently write code that relies on asserts not being stripped, without affecting working code, I’d listen. If you want to try to make such broken code work, while still not breaking existing code, I wouldn’t support the idea but I’d accept it as a valid proposal. But please can we stop assuming it’s OK to break existing, working code, in order to protect people who write code with bugs from the consequences of their errors?
And for that matter can we stop trying to suggest that writing code that assumes a language feature doesn’t work like it’s documented, is somehow OK?
Even if it’s just verifying that nothing breaks. ↩︎
Yes, but there’s a huge gulf between “generates a lot of PRs that we don’t want” and “generates every necessary PR in order to make a change happen”. History shows that, for the most part, warnings don’t get shown to the people who need to deal with them; they’re nothing more than a nuisance. So you’re trying to suggest that, for every project using assertions, someone will create the necessary PR rather than just being frustrated by the warnings.
Nuisance PRs can happen repeatedly for the same project. Necessary PRs can be missed for many projects. These are not in conflict. Both problems could even happen simultaneously for the same change, depending on how each project operates.
Your absolute best hope with this sort of change is that there is a trivial equivalent that will reinstate the old behaviour, one which works on older Pythons as well. This makes the PRs relatively simple, and nothing more than an administrative hassle. (Quite a big adminstrative hassle when you count up the total number of projects, but no more than that.) If there are other concerns, though - like, “this only works on Python 3.X-1 and newer” - then the change becomes both necessary and impossible. Those who are using this tool on newer Pythons require the change to avoid the warning; those using it on older Pythons have to avoid the change. Or if there’s a fully backward-compatible equivalent, but it comes with a performance hit, then every project has to decide whether it can accept that hit.
So… once again, we have a proposal for a backward incompatible change, whose entire justification is “people misuse assert so we need to change something”. That’s not enough justification.
I will admit to being imprecise with my wording. If a test framework relies on assertions and doesn’t change in the 2-3 year deprecation period, it would stop working. But if I understand this correctly, pytest replaces the assert statements in test files already without checking these flags.
I thought I was suggesting such a thing. In this scenario the issue isn’t with -O, it’s with the standard runtime mode. Users of -O will see no change in behavior.
If code is using assertions as-documented (used as invariants, should never raise unless there are bad assumptions), nothing should change and no noise will be created. The relevant changes to them are a) testing frameworks that rely on assert need to be aware of this and b) in any other case where they need assert evaluated, they should use python -X dev rather than python [a.k.a. debug], which makes the use case more explicit.
If some code is using them incorrectly (it raises assertion errors as a part of normal operation, as a way to check input or something), there will be warnings that these uses need to be changed into a proper check that always runs. This tells people who are doing the wrong thing to change their code and make it correct. I’m not trying to “let them off” from writing buggy code. I’m warning exactly those people, and telling them how to fix it. What am I misunderstanding here?
It seems like this topic is too contentious (? weirdly) so I’ll drop it, but I’m honestly confused about these responses to that suggestion.
Yeah, here it is:
- assert condition, "error message"
+ if not condition:
+ raise RuntimeError("error message")
This is backwards compatible for everything, and makes the code read a little cleaner IMO because it’s clear that not X is the problem (as mentioned here).
There’s technically a performance hit for this if you run under python -O but these projects are exactly the projects that don’t use or know about that mode, and their code was incorrect in that situation.
If they are fine with stripping assertions in normal conditions, they don’t have to change anything. But if someone is seeing that warning, then your invariant statements are varying.
I don’t see how this is backwards incompatible, anymore than any deprecation is.
I’m not terribly fussed with making this change, I just thought the proposal above was a reasonably painless way to reconcile the existence of a lot of incorrect code with the intended/documented behavior. It avoids the primary issue above (“don’t punish people for doing it right”/“let people off for doing it wrong”) and provides a clear transition.
def combinations(items, n):
assert n <= len(items)
...
This is flat out wrong (unless this is an internal function or something), but it’s highly likely that it’ll never actually trigger the warning, and if it does, all you get is a warning on top of an assertion error. The ONLY way your proposal would help would be if something is catching this exception - which would mean that there are two things to be fixed.
If this is ~never triggered, what’s the downside to stripping that assertion from runtime?
I guess you’re saying “there will not always be warnings for bad uses of assert” but I didn’t mean to imply that would happen. Warning for the most egregious uses is better than the status quo, I think?
That changes which exception is being raised. Now you have to go and change all your tests to make sure they check for the right exception. Plus, you lose the (often underrated) automatic display of the errant line, since now the line of code in the traceback is the raise, which has no information about the actual assertion. So, “backwards compatible for everything” is a bit inaccurate; this is NOT a “trivial equivalent”, it is a substitution that has to be discussed as a real change to the code.
Erm… not really sure how to answer that. It is backward incompatible. Deprecations are also backward incompatible. Both of them need a lot of justification. What’s your point?
Sorry, I shouldn’t have said “never” there, but “very rarely”. It’ll only be triggered if the function is called with incorrect arguments. So, chances are there won’t ever be that PR that you’re depending on.
As a side note: I am fully aware that, as the Red Queen put it, you have to run as fast as you can just to stay in one place. Keeping code up-to-date as other things change IS a task that has to be done. Sometimes, that does mean making changes because the language is changing and your code needs to do things differently. However, that alone is not enough reason to justify a change. There has to be an actual real benefit from the change, otherwise it’s just the React.js development model.
There are two sides to the compatibility equation: the version of
the interpreter and the version of the script being interpreted. The
proposal would be a backwards-incompatible change in a new version
of the interpreter, even though you could alter the script so that
it’s backwards-compatible to prior interpreter versions.
I think we’ve gotten sufficiently away from the original proposal, which is deprecating the current flags, and introducing a new one which doesn’t strip asserts.
Maybe discussions of an alternate discussion could be had in an alternate thread?
It is already easy to write assertions that run only “in debug”:
assert <expression>, <message>
and possible but a bit harder to write “assertions” that also run “in production”:
if not <expression>:
raise AssertionError(message)
This is the correct prioritization; the purpose of assertions is to help debug the code, and if you have decided that “production code” is allowed to be “optimized”, that entails allowing it to shed the overhead of easily-written code that has a debugging purpose and no intentional effect on semantics.
I agree with @thejcannon that the discussion has gotten far away from the original proposal. Let me try to rephrase it in a way that hopefully exposes the crux of my argument:
-O is a confusing feature that is almost never used and breaks code that was written by people who are not aware of it. Lets deprecate it and create a new flag, -O1, which achieves the same purpose in a more explicit and less confusing way.
This will not break existing code in the sense of making it do something different (except in very extreme edge cases), but it will require a bit of work in very rare cases to fix performance regressions (e.g. I’ve looked at most uses of assert in the stdlib and am not aware of a single one that needs to be changed. The only asserts that I know needs fixing is in the click library.)
Python’s assert was modeled after C’s assert() macro, which gets stripped in release by default for historical reasons, however (1) C students learn about debug and release compilation in literally the first lesson while most Python programmers never learn about -O in their lives and (2) in C land, stripping it in release has not been considered a best practice for a couple of decades.
OK. If this is a new proposal, please explain precisely what -O1 will do and how it differs from -O.
Also, if you’re proposing to deprecate -O, you need to check with the people who actually use that flag, to ensure that they won’t have any issues with it being deprecated. (Hint - they will!)
This is not technically accurate. Asserts get stripped because they are defined as being equivalent to a conditional that checks __debug__, and if __debug__ tests are stripped in -O mode.
Unless you change the definition of the assert statement, its behaviour is fundamentally tied to the behaviour of __debug__. And if you do change the definition of assert, aren’t we back where we started?