From 97ff8893b03ef2de13a99de25ad9b220cab0ffac Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sun, 8 Jul 2018 08:28:47 -0700 Subject: [PATCH] PEP 572: Add Appendix B with translations of various comprehension cases (#700) --- pep-0572.rst | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/pep-0572.rst b/pep-0572.rst index 88f60216a..50c77afe6 100644 --- a/pep-0572.rst +++ b/pep-0572.rst @@ -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 ==========