(original) (raw)

\[Victor Stinner\]Let's say that the PEP 572 (assignment expression) is going to be
approved. Let's move on and see how it can be used in the Python stdlib.

Ugh - how adult ;-)
I propose to start the discussion about "coding style" (where are
assignment expressions appropriate or not?) with the "while True"
case.

I wrote a WIP pull request to use assignment expressions in "while True":
https://github.com/python/cpython/pull/8095/files

In short, replace:

while True:
x = expr
if not x:
break
...
with:

while (x := expr):

Better is to translate it to:

while x := expr:

That is, ;parentheses aren't needed in this context, and adding them anyway will quickly look as strange here as, e.g.,

return (result)

already looks. (Always requiring parens was rejected - see the PEP's "Always requiring parentheses" section). ...
...
\== Pattern 3, double condition ==

while True:
s = self.\_\_read(1)
if not s or s == NUL:
break
....

replaced with:

while (s := self.\_\_read(1)) and s != NUL:
...
Honestly, here, I don't know if it's appropriate...

Then leave it be! My rule was "if it's not obviously better - at least a little - don't use it". This one is a wash (tie) to me, so I'd save the bother of changing it. Or just do the obvious part:

while s := self.\_\_read(1):
if s == NUL:
break

No matter how the code may change in the future, the loop body surely requires a non-empty \`s\` to stare at. and now the \`while\` header makes that abundantly clear at a glance.

...
\== Pattern 4, while (...): pass ==

Sometimes, the loop body is replaced by "pass".

while True:
tarinfo = self.next()
if tarinfo is None:
break

replaced with:

while (tarinfo := self.next()) is not None:
pass

It reminds me the \*surprising\* "while (func());" or "while (func())
{}" in C (sorry for theorical C example, I'm talking about C loops
with an empty body).

Maybe it's acceptable here, I'm not sure.

Note: such loop is rare (see my PR).

I decided "slight loss - don't bother" for most such in my own code. At least the first spelling above cuts the number of statements in half. Replacing random.py's

r = getrandbits(k)
while r >= n:
r = getrandbits(k)

with

while (r := getrandbits(k)) >= n:
pass

is more attractive, for eliminating a textually identical (except for indentation) line.

== Pattern 5, two variables ==

while True:
m = match()
if not m:
break
j = m.end()
if i == j:
break
...

replaced with:

while (m := match()) and (j := m.end()) == i:
...

Maybe we reached here the maximum acceptable complexity of a single
Python line? :-)

It's at my limit. But, as in an earlier example, I'd be tempted to do "the obvious part":

while m:= match():
j = m.end()
if i == j::
break

Then the start reads like "while there's something \_to\_ look at::" and the body of the loop is happily guaranteed that there is.

...
I chose to not use assignment expressions for the following while loops.

(A)

while True:
name, token = \_getname(g)
if not name:
break
...

"x, y := ..." is invalid. It can be tricked using "while (x\_y :=...)\[0\]: x, y = x\_y; ...". IMHO it's not worth it.

Indeed, it's quite worth \_not\_ doing it :-)
(B)

while True:
coeff = \_dlog10(c, e, places)
\# assert len(str(abs(coeff)))-p >= 1
if coeff % (5\*10\*\*(len(str(abs(coeff)))-p-1)):
break
places += 3

NOT replaced with:

while not (coeff := \_dlog10(c, e, places)) % (5\*10\*\*(len(str(abs(coeff)))-p-1)):
places += 3

^-- Tim Peters, I'm looking at you :-)

Not my code ;-) - and it's \_already\_ too "busy" to be my code. The \`5\*10\*\*...\` part is already crying to be broken into simpler pieces with a comment explaining what the intent is.

coeff is defined and then "immediately" used in "y" expression of
x%y... Yeah, it's valid code, but it looks too magic to me...

And the code was already too dense to follow easily.

(C)

while True:
chunk = self.raw.read()
if chunk in empty\_values:
nodata\_val = chunk
break
...

"nodata\_val = chunk" cannot be put into the "chunk := self.raw.read()"
assignment expression combined with a test. At least, I don't see how.

No need to strain, either! If it's not obvious, don't bother.

(D)

while 1:
u1 = random()
if not 1e-7 < u1 < .9999999:
continue
...

Again, I don't see how to use assignment expression here.

It could be, in context, but not for the outermost \`while 1:\`.

while 1:
while not 1e-7 < (u1 := random()) < 9999999:
pass
# code that uses u1, and possibly returns, else goes
# around the outer loop again
That one is fine by me either way.

In all, I'd say our tastes here are pretty similar! So there's hope ;-)