Recently Barry shown an example: assert len(subdirs := list(path.iterdir())) == 0, subdirs It looks awful to me. It looks even worse than using asserts for validating the user input. The assert has a side effect, and it depends on the interpreter option (-O). Even if subdirs is not used outside of the assert *now*, it is easy to introduce an error later, and it is hard to notice it if tests are not ran with the -O option regularly. Does PEP 572 encourages writing such code, discourages this, or completely forbids?
On Tue, Jul 17, 2018 at 6:50 PM, Serhiy Storchaka
Recently Barry shown an example:
assert len(subdirs := list(path.iterdir())) == 0, subdirs
It looks awful to me. It looks even worse than using asserts for validating the user input. The assert has a side effect, and it depends on the interpreter option (-O). Even if subdirs is not used outside of the assert *now*, it is easy to introduce an error later, and it is hard to notice it if tests are not ran with the -O option regularly.
Does PEP 572 encourages writing such code, discourages this, or completely forbids?
Asserts with side effects are already a bad idea. PEP 572 makes no change to this. If you're putting any sort of side effects inside assertions, you're playing with fire. ChrisA
On Tue, 17 Jul 2018 18:56:36 +1000
Chris Angelico
On Tue, Jul 17, 2018 at 6:50 PM, Serhiy Storchaka
wrote: Recently Barry shown an example:
assert len(subdirs := list(path.iterdir())) == 0, subdirs
It looks awful to me. It looks even worse than using asserts for validating the user input. The assert has a side effect, and it depends on the interpreter option (-O). Even if subdirs is not used outside of the assert *now*, it is easy to introduce an error later, and it is hard to notice it if tests are not ran with the -O option regularly.
Does PEP 572 encourages writing such code, discourages this, or completely forbids?
Asserts with side effects are already a bad idea. PEP 572 makes no change to this. If you're putting any sort of side effects inside assertions, you're playing with fire.
Well, PEP 572 makes it easier to put side effects in side assertions since you can now stuff an assignment inside an assert. Which is what Serhiy's / Barry's example shows. Regards Antoine.
2018-07-17 10:50 GMT+02:00 Serhiy Storchaka
assert len(subdirs := list(path.iterdir())) == 0, subdirs
Does PEP 572 encourages writing such code, discourages this, or completely forbids?
If I understood correctly Guido, Python the language must not prevent developers to experiment various usage of assignement expressions. The f-string has a similar design: anything even f'{__import__("os").system("rm -rf /")}' is allowed. It's more the role of linters like flake8 to warn against "bad practices". I don't think that PEPs like f-string and assignment expressions must include *coding styles*. Sure, f-string and assignment expressions allow to write surprising and bad code. But you don't them them to write crappy code :-) I was strongly against f-string at the beginning because I immediately predicted that people will start to call functions (with side effects) in f-string... but I didn't see that happen. In fact, developers are reasonable and use f-string when it's appropriate and safe. Victor
On Tue, Jul 17, 2018 at 1:50 AM, Serhiy Storchaka
Recently Barry shown an example:
assert len(subdirs := list(path.iterdir())) == 0, subdirs
It looks awful to me. It looks even worse than using asserts for validating the user input. The assert has a side effect, and it depends on the interpreter option (-O). Even if subdirs is not used outside of the assert *now*, it is easy to introduce an error later, and it is hard to notice it if tests are not ran with the -O option regularly.
Does PEP 572 encourages writing such code, discourages this, or completely forbids?
The PEP has no specific opinion except it is not forbidden. Personally I like Barry's example just fine -- assuming `subdirs` is not used later, this feels like a good use case. -- --Guido van Rossum (python.org/~guido)
17.07.18 17:59, Guido van Rossum пише:
The PEP has no specific opinion except it is not forbidden.
Personally I like Barry's example just fine -- assuming `subdirs` is not used later, this feels like a good use case.
Shouldn't this problem be solved in the same way as for comprehensions? Should not the assert statement introduce an implicit lexical scope for preventing leaking variables?
On Tue, Jul 17, 2018 at 8:28 AM, Serhiy Storchaka
17.07.18 17:59, Guido van Rossum пише:
The PEP has no specific opinion except it is not forbidden.
Personally I like Barry's example just fine -- assuming `subdirs` is not used later, this feels like a good use case.
Shouldn't this problem be solved in the same way as for comprehensions?
No, it's nothing like those.
Should not the assert statement introduce an implicit lexical scope for preventing leaking variables?
I don't see why. As Chris said, side effects in asserts are nothing new and this PEP is not the one to do something about it. Serhiy, have you even read the latest version of PEP 572? In its current form it does not introduce any implicit scopes and even goes out of its way to make assignments in comprehensions "leak" out of the comprehension scope into the surrounding (explicit, using 'def' or 'lambda') function's scope. -- --Guido van Rossum (python.org/~guido)
I don't see why. As Chris said, side effects in asserts are nothing new and
Indeed -- this new feature makes it easier to affect the local scope in all sorts of new places. It was decided that the additional complexity is worth it to make the language more expressive, and it was also decided not to try to "lock down" this new feature to only "appropriate" places.
this PEP is not the one to do something about it.
Hmm --- not sure if it's PEP-worthy, but I'd sure love to see the community make a strong stance that: asserts are for tests, and only for tests Which doesn't necessarily mean that assert statements only exist in test code (though I personally follow that practice), but it does mean that assert statements, wherever they live, should not be counted on at run time, ever. -CHB -- Christopher Barker, Ph.D. Oceanographer Emergency Response Division NOAA/NOS/OR&R (206) 526-6959 voice 7600 Sand Point Way NE (206) 526-6329 fax Seattle, WA 98115 (206) 526-6317 main reception Chris.Barker@noaa.gov
17.07.18 18:48, Guido van Rossum пише:
On Tue, Jul 17, 2018 at 8:28 AM, Serhiy Storchaka
mailto:storchaka@gmail.com> wrote: Should not the assert statement introduce an implicit lexical scope for preventing leaking variables? I don't see why. As Chris said, side effects in asserts are nothing new and this PEP is not the one to do something about it.
This side effect is new. No other expressions that can be used in asserts leaked local variables before. The only exception is list comprehensions in Python 2, and this was fixed in Python 3. We can't make the assignment expression itself creating its own scope, because this will invalidate its purpose. But the problem with assert ccould be solved by making assert creating a new lexical scope. assert expr, msg could be translated to if __debug__ and not (lambda: expr)(): raise AssertionError(msg) instead of if __debug__ and not expr: raise AssertionError(msg)
On Wed, Jul 18, 2018 at 2:24 AM, Serhiy Storchaka
17.07.18 18:48, Guido van Rossum пише:
On Tue, Jul 17, 2018 at 8:28 AM, Serhiy Storchaka
mailto:storchaka@gmail.com> wrote: Should not the assert statement introduce an implicit lexical scope for preventing leaking variables? I don't see why. As Chris said, side effects in asserts are nothing new and this PEP is not the one to do something about it.
This side effect is new. No other expressions that can be used in asserts leaked local variables before. The only exception is list comprehensions in Python 2, and this was fixed in Python 3.
There are plenty of expressions that have side effects, though. Creating local variables is just one type of side effect. If assertions are being misused, that is the fault of people writing bad assertions, not of assignment expressions, dict.setdefault(), subprocess.call(), or any other operation. Point of interest: You're basically asking for statement-local assignment, which was one of PEP 572's earliest forms. It received even more backlash than the current version did. The wheel turns and the same spoke comes up... ChrisA
On Tue, Jul 17, 2018 at 9:24 AM, Serhiy Storchaka
17.07.18 18:48, Guido van Rossum пише:
On Tue, Jul 17, 2018 at 8:28 AM, Serhiy Storchaka
mailto:storchaka@gmail.com> wrote: Should not the assert statement introduce an implicit lexical scope for preventing leaking variables? I don't see why. As Chris said, side effects in asserts are nothing new and this PEP is not the one to do something about it.
This side effect is new. No other expressions that can be used in asserts leaked local variables before. The only exception is list comprehensions in Python 2, and this was fixed in Python 3.
We can't make the assignment expression itself creating its own scope, because this will invalidate its purpose. But the problem with assert ccould be solved by making assert creating a new lexical scope.
assert expr, msg
could be translated to
if __debug__ and not (lambda: expr)(): raise AssertionError(msg)
instead of
if __debug__ and not expr: raise AssertionError(msg)
I don't believe this "problem" needs to be solved. It seems to be you are seeking an excuse to reopen the acrimonious PEP 572 discussion. Please stop. -- --Guido van Rossum (python.org/~guido)
On Tue, Jul 17, 2018 at 07:24:12PM +0300, Serhiy Storchaka wrote:
17.07.18 18:48, Guido van Rossum пише:
On Tue, Jul 17, 2018 at 8:28 AM, Serhiy Storchaka
mailto:storchaka@gmail.com> wrote: Should not the assert statement introduce an implicit lexical scope for preventing leaking variables? I don't see why. As Chris said, side effects in asserts are nothing new and this PEP is not the one to do something about it.
This side effect is new. No other expressions that can be used in asserts leaked local variables before.
assert vars().__setitem__('foo', 42) or foo Not that anyone would write such a thing (and it probably won't work inside a function in CPython), but it is possible. Besides, there is a legitimate use for assignment expressions in assertions: assert (spam := something) > 2, 'not enough spam' assert sequence[foo] == 999, 'sequence item isn't 999' Sometimes you need two assertions.
We can't make the assignment expression itself creating its own scope, because this will invalidate its purpose. But the problem with assert ccould be solved by making assert creating a new lexical scope.
Assertions could be used to perform imports too: assert __import__('math') but people don't do that either. Let's not try fixing "problems" that won't exist. Most people have more sense than to do that. And for those who don't and misuse assertions, well, they've been misusing them for two decades and the sky hasn't fallen.
assert expr, msg
could be translated to
if __debug__ and not (lambda: expr)(): raise AssertionError(msg)
YAGNI. I do a lot of work in the REPL where I'm not afraid to bend the rules and use hacks I'd never use in long-lasting code. I'd never use assert for testing input in real code, but I do it in the REPL all the time. If I choose to abuse assertions in the REPL, you ought to trust me to do this responsibly. I'm a consenting adult. If I want to run with scissors, point a loaded gun at my foot, put all my eggs in one basket, or use assignment expressions in an assertion, I don't want the interpreter telling me I can't. -- Steve
On Wed, Jul 18, 2018 at 03:41:08AM +1000, Steven D'Aprano wrote:
Besides, there is a legitimate use for assignment expressions in assertions:
assert (spam := something) > 2, 'not enough spam' assert sequence[foo] == 999, 'sequence item isn't 999'
Ah, obviously the index in the second line ought to be spam, not foo. -- Steve
On Jul 17, 2018, at 07:59, Guido van Rossum
Personally I like Barry's example just fine -- assuming `subdirs` is not used later, this feels like a good use case.
Thanks! I thought it was cute. It was just something that occurred to me as I was reviewing some existing code. The intent wasn’t to use `subdirs` outside of the assert statement, but I’m warm to it because it means I don’t have to do wasted work outside of the assert statement, or repeat myself in the assert message part. -Barry
[Barry Warsaw]
Thanks! I thought it was cute. It was just something that occurred to me as I was reviewing some existing code. The intent wasn’t to use `subdirs` outside of the assert statement, but I’m warm to it because it means I don’t have to do wasted work outside of the assert statement, or repeat myself in the assert message part.
Because the latter ("repeat myself") is probably more tempting, I'll just note that the "laziness" of using an assignment expression instead may well have nudged you toward writing _better_ code too. assert len(subdirs := list(path.iterdir())) == 0, subdirs Assuming the result of list(path.iterdir()) can change over time (seems very likely), assert len(list(path.iterdir())) == 0, list(path.iterdir()) _could_ end up both triggering and displaying an empty list in the exception detail. The assignment-expression version cannot.
On Jul 17, 2018, at 11:34, Tim Peters
Assuming the result of list(path.iterdir()) can change over time (seems very likely),
assert len(list(path.iterdir())) == 0, list(path.iterdir())
_could_ end up both triggering and displaying an empty list in the exception detail. The assignment-expression version cannot.
Tim, you’re dangerously tempting me into +1 territory. :) -Barry
On 2018-07-17 19:34, Tim Peters wrote:
[Barry Warsaw]
Thanks! I thought it was cute. It was just something that occurred to me as I was reviewing some existing code. The intent wasn’t to use `subdirs` outside of the assert statement, but I’m warm to it because it means I don’t have to do wasted work outside of the assert statement, or repeat myself in the assert message part.
Because the latter ("repeat myself") is probably more tempting, I'll just note that the "laziness" of using an assignment expression instead may well have nudged you toward writing _better_ code too.
assert len(subdirs := list(path.iterdir())) == 0, subdirs
Assuming the result of list(path.iterdir()) can change over time (seems very likely),
assert len(list(path.iterdir())) == 0, list(path.iterdir())
_could_ end up both triggering and displaying an empty list in the exception detail. The assignment-expression version cannot.
Why use len(...) == 0 instead of not(...)? assert not(subdirs := list(path.iterdir())), subdirs
On Jul 17, 2018, at 12:18, MRAB
Why use len(...) == 0 instead of not(...)?
assert not(subdirs := list(path.iterdir())), subdirs
Off-topic, but it’s a style thing. Some style guides (and my personal preference) require to be more explicit when checking for empty sequences. I’ll use a boolean test (e.g. `not` or false-iness) when the subject could be empty, None, or the empty string, and `len() == 0` when the subject is supposed to be a sequence. -Barry
[Serhiy Storchaka]
Recently Barry shown an example:
assert len(subdirs := list(path.iterdir())) == 0, subdirs
It looks awful to me. It looks even worse than using asserts for validating the user input. The assert has a side effect, and it depends on the interpreter option (-O). Even if subdirs is not used outside of the assert *now*, it is easy to introduce an error later, and it is hard to notice it if tests are not ran with the -O option regularly.
Does PEP 572 encourages writing such code, discourages this, or
completely forbids?
The body of the PEP specifies semantics. My Appendix A gives some _opinions_ about "good" and "bad" uses, which boil down to "if it's not obviously at least a little win, don't use it". I can't really guess whether the above is an obvious win or not without context. It is a win (to my eyes) if the code it replaced was if __debug__: subdirs = list(path.iterdir()) assert len(subdirs) == 0, subdirs in which case the semantics are the same in either spelling, with or without -O, but the spelling at the top is less annoying ;-)
17.07.18 19:25, Tim Peters пише:
It is a win (to my eyes) if the code it replaced was
if __debug__: subdirs = list(path.iterdir()) assert len(subdirs) == 0, subdirs
in which case the semantics are the same in either spelling, with or without -O, but the spelling at the top is less annoying ;-)
I completely agree.
participants (10)
-
Antoine Pitrou
-
Barry Warsaw
-
Chris Angelico
-
Chris Barker
-
Guido van Rossum
-
MRAB
-
Serhiy Storchaka
-
Steven D'Aprano
-
Tim Peters
-
Victor Stinner