[Python-Dev] Informal educator feedback on PEP 572 (was Re: 2018 Python Language Summit coverage, last part)
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.
> >?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
> > (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
> [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
> > 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
> > 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
> > 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 : x = 0
> > In : [x for x in range(3)]
> > Out: [0, 1, 2]
> >In : x
> > Out: 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
>> [(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