[Python-ideas] A real life example of "given"

Steven D'Aprano steve at pearwood.info
Thu May 31 20:42:01 EDT 2018


On Thu, May 31, 2018 at 11:03:56AM -0700, Brendan Barnwell wrote:

> 	What I don't understand is this: if we believe that, then why was 
> comprehension-leaking EVER removed?  Everything that I've seen 
> advocating for this kind of leaking seems to me like it is much more 
> logically consistent with allowing all comprehension variables to leak 
> than it is with the current behavior, in which they don't leak.

Originally list comprehensions ran in the same scope as their 
surroundings. In Python 2:

py> [1 for x in ("spam", "eggs")]
[1, 1]
py> x
'eggs'

but when generator expressions were introduced a few years later, they 
ran in their own sub-local scope. This was, I believe, initially 
introduced to simplify the common situation that you return a generator 
from inside a function:

def factory():
    x = "something big"
    return (x for x in seq)

would require holding onto a closure of the factory locals, including 
the "something big" object. Potentially forever, if the generator 
expression is never iterated over.

(I hope someone will correct me if I have misunderstood.)

To avoid that, the x in the generator was put into a distinct scope from 
the x in factory. Either way, the PEP introducing generator expressions 
makes it clear that it was a deliberate decision:

    The loop variable (if it is a simple variable or a tuple of
    simple variables) is not exposed to the surrounding function.
    This facilitates the implementation and makes typical use
    cases more reliable.

https://www.python.org/dev/peps/pep-0289/


In the case of loop variables, there is an argument from "practicality 
beats purity" that they ought to run in their own scope. Loop variables 
tend to be short, generic names like "i", "x", "obj", all the more 
likely to clash with names in the surrounding scope, and hard to spot 
when they do. They're also likely to be used inside loops:

    for x in [1, 2, 3, 4]:
        alist = [expr for x in range(50)]
        # oops, x has been accidentally overridden (in Python 2)
        
I don't *entirely* buy that argument, and I occasionally find it useful 
to inspect the loop variable of a list comprehension after it has run, 
but this is one windmill I'm not going to tilt against. Reversing the 
decision to put the loop variables in their own sublocal scope is *not* 
part of this PEP.

But this is less likely to be a problem for explicit assignment. Outside 
of toy examples, we're more likely to assign to descriptive names and 
less likely to clash with any surrounding loop variable:

    for book in library:
        text = [content for chapter in books.chapters() 
                        if (content := chapter.get_text(all=True) 
                        and re.match(pattern, content)]

Given an obvious and explicit assignment to "chapter", say, we are more 
likely to realise when we are reusing a name (compared to assigning to 
a loop variable i, say). At least, we should be no more likely to mess 
this up than we are for any other local-level assignment.



-- 
Steve


More information about the Python-ideas mailing list