[Python-Dev] Informal educator feedback on PEP 572 (was Re: 2018 Python Language Summit coverage, last part)

Terry Reedy tjreedy at udel.edu
Fri Jun 29 00:57:51 EDT 2018

On 6/28/2018 11:21 PM, Tim Peters wrote:

[somewhere below] this is the last time I'm going to repeat it all again ;-)

For me, this is your most convincing exposition and summary of why the 
proposal is at least ok.  Thank you.

> [Chris]
>  > yes, it was a contrived example, but the simplest one I could think 
> of off
>  > the top of my head that re-bound a name in the loop -- which was what I
>  > thought was the entire point of this discussion?
> But why off the top of your head?  There are literally hundreds & 
> hundreds of prior messages about this PEP, not to mention that you could 
> also find examples in the PEP.  Why make up a senseless example?
>  > If we think hardly anyone is ever going to do that -- then I guess it 
> doesn't matter
>  > how it's handled.
> So look at real examples.  One that's been repeated at least a hundred 
> times wants a local to "leak into" a listcomp:
> total = 0
> cumsums = [total ::= total + value for value in data]
> As an educator, how are you going to explain that blowing up with 
> UnboundLocalError instead?  Do you currently teach that comprehensions 
> and genexps are implemented via invisible magically generated lexically 
> nested functions?  If not, you're going to have to start for people to 
> even begin to make sense of UnboundLocalError if `total` _doesn't_ "leak 
> into" that example.  My belief is that just about everyone who doesn't 
> know "too much" about the current implementation will be astonished & 
> baffled if that example doesn't "just work".
> In other cases it's desired that targets "leak out":
> while any(n % (divisor := p) == 0 for p in small_primes):
>      n //= divisor
> And in still other cases no leaking (neither in nor out) is desired.
> Same as `for` targets in that way,. but in the opposite direction:  they 
> don't leak and there's no way to make them leak, not even when that's 
> wanted.  Which _is_ wanted in the last example above, which would be 
> clearer still written as:
> while any(n % p == 0 for p in small_primes):
>      n //= p
> But that ship has sailed.
>  > ...
>  > And "nonlocal" is not used that often, and when it is it's for 
> careful closure
>  > trickery -- I'm guessing := will be far more common.
> My guess (recorded in the PEP's Appendix A) is that assignment 
> expressions _overall_ will be used more often than ternary `if` but 
> significantly less often than augmented assignment.  I expect their use 
> in genexps and comprehensions will be minimal.  There are real use cases 
> for them, but the vast majority of genexps and comprehensions apparently 
> have no use for them at all.
>  > And, of course, when a newbie encounters it, they can google it and 
> see what
>  > it means -- far different that seeing a := in a comprehension and 
> understanding
>  > (by osmosis??) that it might make changes in the local scope.
> Which relates to the above:  how do you teach these things?  The idea 
> that "a newbie" even _suspects_ that genexps and listcomps have 
> something to do with lexically nested scopes and invisible nested 
> functions strikes me as hilarious ;-)
> Regardless of how assignment expressions work in listcomps and genexps, 
> this example (which uses neither) _will_ rebind the containing block's `x`:
> [x := 1]
> How then are you going to explain that this seemingly trivial variation 
> _doesn't_?
> [x := 1 for ignore in "a"]
> For all the world they both appear to be binding `x` in the code block 
> containing the brackets.  So let them.
> Even worse,
> [x for ignore in range(x := 1)]
> will rebind `x` in the containing block _regardless_ of how assignment 
> expression targets are treated in "most of" a comprehension, because the 
> expression defining the iterable of the outermost "for" _is_ evaluated 
> in the containing block (it is _not_ evaluated in the scope of the 
> synthetic function).
> That's not a special case for targets if they all "leak", but is if they 
> don't.
>  > And  I don't think you can even do that with generator expressions 
> now -- as
>  > they can only contain expressions.
> Expressions can invoke arbitrary functions, which in turn can do 
> anything whatsoever.
>  > Which is my point -- this would allow the local namespace to be 
> manipulated
>  > in places it never could before.
> As above, not true.  However, it would make it _easier_ to write 
> senseless code mucking with the local namespace - if that's what you 
> want to do.
>  > Maybe it's only comprehensions, and maybe it'll be rare to have a 
> confusing
>  > version of those, so it'll be no big deal, but this thread started 
> talking about
>  > educators' take on this -- and as an educator, I think this really does
>  > complicate the language.
> I'll grant that it certainly doesn't simplify the language ;-)
>  > Python got much of it's "fame" by being "executable pseudo code" -- 
> its been
>  > moving farther and farther away from those roots. That's generally a 
> good thing,
>  > as we've gain expressiveness in exchangel, but we shouldn't pretend 
> it isn't
>  > happening, or that this proposal doesn't contribute to that trend.
> I didn't say a word about that one way or the other.  I mostly agree, 
> but at the start Guido was aiming to fill a niche between shell 
> scripting languages and C.  It was a very "clean" language from the 
> start, but not aimed at beginners.  Thanks to his experience working on 
> ABC, it carried over some key ideas that were beginner-friendly, though.
> I view assignment expressions as being aimed at much the same audience 
> as augmented assignments:  experienced programmers who already know the 
> pros and cons from vast experience with them in a large number of other 
> widely used languages.  That's also a key Python audience.
>  > ...
>  > Well, I've been surprised by what confused students before, and I 
> will again. But I
>  > dont hink there is any doubt that Python 3.7 is a notably harder to 
> learn that
>  > Python 1.5 was...
> Absolutely.  It doesn't much bother me, though - at this point the 
> language and its widely used libraries are so sprawling that I doubt 
> anyone is fluent in all of it.  That's a sign of worldly success.
>  > ...
>  > and this:
>  >
>  > In [55]: x = 0
>  > In [56]: [x for x in range(3)]
>  > Out[56]: [0, 1, 2]
>  >In [57]: x
>  > Out[57]: 0
>  >
>  > doesn't change x in the local scope --
> In Python 3, yes; in Python 2 it rebinds `x` to 2.
>  > if that was a good idea, why is a good idea
>  >  to have := in a comprehension effect the local scope??
> Because you can't write a genexp or comprehension AT ALL without 
> specifying `for` targets, and in the overwhelming majority of genexps 
> and comprehensions anyone ever looked at, "leaking" of for-targets was 
> not wanted.  "So don't let them leak" was pretty much a no-brainer for 
> Python 3.
> But assignment expressions are NEVER required to write a genexp or 
> comprehension, and there are only a handful of patterns known so far in 
> which assignment expressions appear to be of real value in those 
> contexts.  In at least half those patterns, leaking _is_ wanted - 
> indeed, essential.  In the rest, leaking isn't.
> So it goes.  Also don't ignore other examples given before, showing how 
> having assignment expressions _at all_ argues for "leaking" in order to 
> be consistent with what assignment expressions do outside of 
> comprehensions and genexps.
>  > But maybe it is just me.
> Nope.  But it has been discussed so often before this is the last time 
> I'm going to repeat it all again ;-)

>  > ...
>  > One of these conversations was started with an example something like 
> this:
>  >
>> [(f(x), g(f(x))) for x in an_iterable]
>  >
>  > The OP didn't like having to call f() twice. So that would become:
>  >
>> [ (temp:=f(x), g(temp)) for x in an_iterable]
>  >
>  > so now the question is: should "temp" be created / changed in the 
> enclosing local scope?
>  >
>  > This sure looks a lot like letting the iteration name (x in this 
> example) leak out -
>  > so I'd say no.
> In that example, right, leaking `temp` almost certainly isn't wanted.  
> So it goes.
>  > And I don't think this kind of thing would be rare.
> I do.  It's dead easy to make up examples to "prove" anything people 
> like, but I'm unswayed unless examples come from real code, or are 
> obviously compelling.
> Since we're not going to get a way to explicitly say which targets 
> (neither `for` nor assignment expression) do and don't leak, it's a 
> reasonably satisfying compromise to say that one kind never leaks and 
> the other kind always leaks.  The pick your poison accordingly.
> In the example above, note that they _could_ already do, e.g.,
>     [(fx, g(fx)) for x in an_iterable for fx in [f(x)]]
> Then nothing leaks (well, unless f() or g() do tricky things).  I 
> personally wouldn't care that `temp` leaks - but then I probably would 
> have written that example as the shorter  (& clearer to my eyes):
>      [(v. g(v)) for v in map(f, an_iterable)]
> to begin with.

Terry Jan Reedy

More information about the Python-Dev mailing list