Skip to content

Commit

Permalink
PEP 572: Document the changes to comprehensions
Browse files Browse the repository at this point in the history
  • Loading branch information
Rosuav committed Apr 11, 2018
1 parent 18b869d commit 71fea57
Showing 1 changed file with 101 additions and 2 deletions.
103 changes: 101 additions & 2 deletions pep-0572.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,97 @@ Augmented assignment is not supported in expression form::
Otherwise, the semantics of assignment are unchanged by this proposal.


Alterations to comprehensions
-----------------------------

The current behaviour of list/set/dict comprehensions and generator
expressions has some edge cases that would behave strangely if an assignment
expression were to be used. Therefore the proposed semantics are changed,
removing the current edge cases, and instead altering their behaviour *only*
in a class scope.

As of Python 3.7, the outermost iterable of any comprehension is evaluated
in the surrounding context, and then passed as an argument to the implicit
function that evaluates the comprehension.

Under this proposal, the entire body of the comprehension is evaluated in
its implicit function. Names not assigned to within the comprehension are
located in the surrounding scopes, as with normal lookups. As one special
case, a comprehension at class scope will **eagerly bind** any name which
is already defined in the class scope.

A list comprehension can be unrolled into an equivalent function. With
Python 3.7 semantics::

numbers = [x + y for x in range(3) for y in range(4)]
# Is approximately equivalent to
def <listcomp>(iterator):
result = []
for x in iterator:
for y in range(4):
result.append(x + y)
return result
numbers = <listcomp>(iter(range(3)))

Under the new semantics, this would instead be equivalent to:

def <listcomp>():
result = []
for x in range(3):
for y in range(4):
result.append(x + y)
return result
numbers = <listcomp>()

When a class scope is involved, a naive transformation into a function would
prevent name lookups (as the function would behave like a method).

class X:
names = ["Fred", "Barney", "Joe"]
prefix = "> "
prefixed_names = [prefix + name for name in names]

With Python 3.7 semantics, this will evaluate the outermost iterable at class
scope, which will succeed; but it will evaluate everything else in a function::

class X:
names = ["Fred", "Barney", "Joe"]
prefix = "> "
def <listcomp>(iterator):
result = []
for name in iterator:
result.append(prefix + name)
return result
prefixed_names = <listcomp>(iter(names))

The name ``prefix`` is thus searched for at global scope, ignoring the class
name. Under the proposed semantics, this name will be eagerly bound, being
approximately equivalent to::

class X:
names = ["Fred", "Barney", "Joe"]
prefix = "> "
def <listcomp>(prefix=prefix):
result = []
for name in names:
result.append(prefix + name)
return result
prefixed_names = <listcomp>()

With list comprehensions, this is unlikely to cause any confusion. With
generator expressions, this has the potential to affect behaviour, as the
eager binding means that the name could be rebound between the creation of
the genexp and the first call to ``next()``. It is, however, more closely
aligned to normal expectations. The effect is ONLY seen with names that
are looked up from class scope; global names (eg ``range()``) will still
be late-bound as usual.

One consequence of this change is that certain bugs in genexps will not
be detected until the first call to ``next()``, where today they would be
caught upon creation of the generator. TODO: Discuss the merits and costs
of amelioration proposals.


Recommended use-cases
=====================

Expand Down Expand Up @@ -136,8 +227,8 @@ an ``if`` or ``while`` statement::
# Current Python, capturing return value - four-line loop header
while True:
command = input("> ");
if command == "quit":
break
if command == "quit":
break
print("You entered:", command)

# Proposed alternative to the above
Expand Down Expand Up @@ -311,6 +402,14 @@ The assignment statement is a clear declaration of intent: this value is to
be assigned to this target, and that's it.


Acknowledgements
================

The author wishes to thank Guido van Rossum and Nick Coghlan for their
considerable contributions to this proposal, and to members of the
core-mentorship mailing list for assistance with implementation.


References
==========

Expand Down

0 comments on commit 71fea57

Please sign in to comment.