[Python-Dev] Assignment expression and coding style: the while True case (original) (raw)

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 (xy :=...)[0]: x, y = xy; ...". 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 % (510*(len(str(abs(coeff)))-p-1)): break places += 3 NOT replaced with: while not (coeff := dlog10(c, e, places)) % (510*(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 emptyvalues: nodataval = chunk break ... "nodataval = 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