[Python-Dev] Assignment expression and coding style: the while True case

Tim Peters tim.peters at gmail.com
Wed Jul 4 20:25:54 EDT 2018


[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
<https://www.python.org/dev/peps/pep-0572/#id24>" 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 ;-)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20180704/2f6e738b/attachment.html>


More information about the Python-Dev mailing list