PEP 572: Add Appendix B with translations of various comprehension cases (#700)

This commit is contained in:
Guido van Rossum 2018-07-08 08:28:47 -07:00 committed by GitHub
parent 52113333bd
commit 97ff8893b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 130 additions and 1 deletions

View File

@ -259,7 +259,8 @@ this special case may be removed from the specification of assignment
expressions. Note that the problem already exists for *using* a
variable defined in the class scope from a comprehension.)
TODO: nested comprehensions
See Appendix B for some examples of how the rules for targets in
comprehensions translate to equivalent code.
TODO: use a a subclass of SyntaxError for various prohibitions?
@ -1027,6 +1028,134 @@ To my eyes, the original form is harder to understand::
return a
Appendix B: Rough code translations for comprehensions
======================================================
This appendix attempts to clarify (though not specify) the rules when
a target occurs in a comprehension or in a generator expression.
For a number of illustrative examples we show the original code,
containing a comprehension, and the translation, where the
comprehension has been replaced by an equivalent generator function
plus some scaffolding.
Since ``[x for ...]`` is equivalent to ``list(x for ...)`` these
examples all use list comprehensions without loss of generality.
And since these examples are meant to clarify edge cases of the rules,
they aren't trying to look like real code.
Note: comprehensions are already implemented via synthesizing nested
generator functions like those in this appendix. The new part is
adding appropriate declarations to establish the intended scope of
assignment expression targets (the same scope they resolve to as if
the assignment were performed in the block containing the outermost
comprehension). For type inference purposes, these illustrative
expansions do not imply that assignment expression targets are always
Optional (but they do indicate the target binding scope).
Let's start with a reminder of what code is generated for a generator
expression without assignment expression.
- Original code (EXPR usually references VAR)::
def f():
a = [EXPR for VAR in ITERABLE]
- Translation (let's not worry about name conflicts)::
def f():
def genexpr(iterator):
for VAR in iterator:
yield EXPR
a = list(genexpr(iter(ITERABLE)))
Let's add a simple assignment expression.
- Original code::
def f():
a = [TARGET := EXPR for VAR in ITERABLE]
- Translation::
def f():
if False:
TARGET = None # Dead code to ensure TARGET is a local variable
def genexpr(iterator):
nonlocal TARGET
for VAR in iterator:
TARGET = EXPR
yield TARGET
a = list(genexpr(iter(ITERABLE)))
Let's add a ``global TARGET`` declaration in ``f()``.
- Original code::
def f():
global TARGET
a = [TARGET := EXPR for VAR in ITERABLE]
- Translation::
def f():
global TARGET
def genexpr(iterator):
global TARGET
for VAR in iterator:
TARGET = EXPR
yield TARGET
a = list(genexpr(iter(ITERABLE)))
Or instead let's add a ``nonlocal TARGET`` declaration in ``f()``.
- Original code::
def g():
TARGET = ...
def f():
nonlocal TARGET
a = [TARGET := EXPR for VAR in ITERABLE]
- Translation::
def g():
TARGET = ...
def f():
nonlocal TARGET
def genexpr(iterator):
nonlocal TARGET
for VAR in iterator:
TARGET = EXPR
yield TARGET
a = list(genexpr(iter(ITERABLE)))
Finally, let's nest two comprehensions.
- Original code::
def f():
a = [[TARGET := i for i in range(3)] for j in range(2)]
# I.e., a = [[0, 1, 2], [0, 1, 2]]
print(TARGET) # prints 2
- Translation::
def f():
if False:
TARGET = None
def outer_genexpr(outer_iterator):
nonlocal TARGET
def inner_generator(inner_iterator):
nonlocal TARGET
for i in inner_iterator:
TARGET = i
yield i
for j in outer_iterator:
yield list(inner_generator(range(3)))
a = list(outer_genexpr(range(2)))
print(TARGET)
References
==========