PEP 572: Reword and update in response to mailing list changes

This commit is contained in:
Chris Angelico 2018-03-23 20:57:38 +11:00
parent d43b984e02
commit 63be12066a
1 changed files with 84 additions and 49 deletions

View File

@ -6,7 +6,7 @@ Type: Standards Track
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 28-Feb-2018 Created: 28-Feb-2018
Python-Version: 3.8 Python-Version: 3.8
Post-History: 28-Feb-2018, 02-Mar-2018 Post-History: 28-Feb-2018, 02-Mar-2018, 23-Mar-2018
Abstract Abstract
@ -36,19 +36,19 @@ actual value available within the loop body.
Syntax and semantics Syntax and semantics
==================== ====================
In any context where arbitrary Python expressions can be used, a named In any context where arbitrary Python expressions can be used, a **named
expression can appear. This must be parenthesized for clarity, and is of expression** can appear. This must be parenthesized for clarity, and is of
the form ``(expr as NAME)`` where ``expr`` is any valid Python expression, the form ``(expr as NAME)`` where ``expr`` is any valid Python expression,
and ``NAME`` is a simple name. and ``NAME`` is a simple name.
The value of such a named expression is the same as the incorporated The value of such a named expression is the same as the incorporated
expression, with the additional side-effect that NAME is bound to that expression, with the additional side-effect that NAME is bound to that
value in all retrievals for the remainder of the current statement. value for the remainder of the current statement.
Just as function-local names shadow global names for the scope of the Just as function-local names shadow global names for the scope of the
function, statement-local names shadow other names for that statement. function, statement-local names shadow other names for that statement.
They can also shadow each other, though actually doing this should be (They can technically also shadow each other, though actually doing this
strongly discouraged in style guides. should not be encouraged.)
Assignment to statement-local names is ONLY through this syntax. Regular Assignment to statement-local names is ONLY through this syntax. Regular
assignment to the same name will remove the statement-local name and assignment to the same name will remove the statement-local name and
@ -88,8 +88,10 @@ expectations. Some examples::
def run_cmd(cmd=cmd): # Capture the value in the default arg def run_cmd(cmd=cmd): # Capture the value in the default arg
print("Running command", cmd) # Works print("Running command", cmd) # Works
Some of these examples should be considered *bad code* and rejected by code Function bodies, in this respect, behave the same way they do in class scope;
review and/or linters; they are not, however, illegal. assigned names are not closed over by method definitions. Defining a function
inside a loop already has potentially-confusing consequences, and SLNBs do not
materially worsen the existing situation.
Differences from regular assignment statements Differences from regular assignment statements
@ -110,6 +112,11 @@ important distinctions.
* An SLNB cannot be the target of any form of assignment, including augmented. * An SLNB cannot be the target of any form of assignment, including augmented.
Attempting to do so will remove the SLNB and assign to the fully-scoped name. Attempting to do so will remove the SLNB and assign to the fully-scoped name.
In many respects, an SLNB is akin to a local variable in an imaginary nested
function, except that the overhead of creating and calling a function is
bypassed. As with names bound by ``for`` loops inside list comprehensions,
SLNBs cannot "leak" into their surrounding scope.
Example usage Example usage
============= =============
@ -126,7 +133,7 @@ These list comprehensions are all approximately equivalent::
# Inline helper function # Inline helper function
stuff = [(lambda y: [y,x/y])(f(x)) for x in range(5)] stuff = [(lambda y: [y,x/y])(f(x)) for x in range(5)]
# Extra 'for' loop - see also Serhiy's optimization # Extra 'for' loop - potentially could be optimized internally
stuff = [[y, x/y] for x in range(5) for y in [f(x)]] stuff = [[y, x/y] for x in range(5) for y in [f(x)]]
# Iterating over a genexp # Iterating over a genexp
@ -143,7 +150,7 @@ These list comprehensions are all approximately equivalent::
for x in range(5): for x in range(5):
y = f(x) y = f(x)
yield [y, x/y] yield [y, x/y]
stuff = list(g) stuff = list(g())
# Using a statement-local name # Using a statement-local name
stuff = [[(f(x) as y), x/y] for x in range(5)] stuff = [[(f(x) as y), x/y] for x in range(5)]
@ -205,48 +212,52 @@ run-time cost is strongly recommended, as is a minimal compile-time cost in
the case where no SLNBs are used. the case where no SLNBs are used.
Open questions Forbidden special cases
============== =======================
1. What happens if the name has already been used? ``(x, (1 as x), x)`` In two situations, the use of SLNBs makes no sense, and could be confusing due
Currently, prior usage functions as if the named expression did not to the ``as`` keyword already having a different meaning in the same context.
exist (following the usual lookup rules); the new name binding will
shadow the other name from the point where it is evaluated until the
end of the statement. Is this acceptable? Should it raise a syntax
error or warning?
2. Syntactic confusion in ``except`` statements. While technically 1. Exception catching::
unambiguous, it is potentially confusing to humans. In Python 3.7,
parenthesizing ``except (Exception as e):`` is illegal, and there is no
reason to capture the exception type (as opposed to the exception
instance, as is done by the regular syntax). Should this be made
outright illegal, to prevent confusion? Can it be left to linters?
It may also (and independently) be of value to use a subscope for the
normal except clause binding, such that ``except Exception as e:`` will
no longer unbind a previous use of the name ``e``.
3. Similar confusion in ``with`` statements, with the difference that there try:
is good reason to capture the result of an expression, and it is also ...
very common for ``__enter__`` methods to return ``self``. In many cases, except (Exception as e1) as e2:
``with expr as name:`` will do the same thing as ``with (expr as name):``, ...
adding to the confusion.
4. Should closures be able to refer to statement-local names? Either way, The expression ``(Exception as e1)`` has the value ``Exception``, and
there will be edge cases that make no sense. Assigning to a name will creates an SLNB ``e1 = Exception``. This is generally useless, and creates
"push through" the SLNB and bind to the regular name; this means that a the potential confusion in that these two statements do quite different
statement ``x = x`` will promote the SLNB to full name, and thus has an things:
impact. Closing over statement-local names, however, introduces scope
and lifetime confusions, as it then becomes possible to have two functions except (Exception as e1):
in almost the same context, closing over the same name, referring to two except Exception as e2:
different cells.
The latter captures the exception **instance**, while the former captures
the ``Exception`` **type** (not the type of the raised exception).
2. Context managers::
lock = threading.Lock()
with (lock as l) as m:
...
This captures the original Lock object as ``l``, and the result of calling
its ``__enter__`` method as ``m``. As with ``except`` statements, this
creates a situation in which parenthesizing an expression subtly changes
its semantics, with the additional pitfall that this will frequently work
(when ``x.__enter__()`` returns x, eg with file objects).
Both of these are forbidden; creating SLNBs in the headers of these statements
will result in a SyntaxError.
Alternative proposals Alternative proposals
===================== =====================
Proposals of this nature have come up frequently on python-ideas. Below are Proposals broadly similar to this one have come up frequently on python-ideas.
a number of alternative syntaxes, some of them specific to comprehensions, Below are a number of alternative syntaxes, some of them specific to
which have been rejected in favour of the one given above. comprehensions, which have been rejected in favour of the one given above.
1. ``where``, ``let``, ``given``:: 1. ``where``, ``let``, ``given``::
@ -260,7 +271,7 @@ which have been rejected in favour of the one given above.
greatest potential for conflict (eg SQLAlchemy and numpy have ``where`` greatest potential for conflict (eg SQLAlchemy and numpy have ``where``
methods, as does ``tkinter.dnd.Icon`` in the standard library). methods, as does ``tkinter.dnd.Icon`` in the standard library).
2. ``with``:: 2. ``with NAME = EXPR``::
stuff = [(y, x/y) with y = f(x) for x in range(5)] stuff = [(y, x/y) with y = f(x) for x in range(5)]
@ -268,24 +279,29 @@ which have been rejected in favour of the one given above.
no additional language keyword. Is restricted to comprehensions, though, no additional language keyword. Is restricted to comprehensions, though,
and cannot as easily be transformed into "longhand" for-loop syntax. Has and cannot as easily be transformed into "longhand" for-loop syntax. Has
the C problem that an equals sign in an expression can now create a name the C problem that an equals sign in an expression can now create a name
binding, rather than performing a comparison. binding, rather than performing a comparison. Would raise the question of
why "with NAME = EXPR:" cannot be used as a statement on its own.
3. ``with... as``:: 3. ``with EXPR as NAME``::
stuff = [(y, x/y) with f(x) as y for x in range(5)] stuff = [(y, x/y) with f(x) as y for x in range(5)]
As per option 2, but using ``as`` in place of the equals sign. Aligns As per option 2, but using ``as`` rather than an equals sign. Aligns
syntactically with other uses of ``as`` for name binding, but a simple syntactically with other uses of ``as`` for name binding, but a simple
transformation to for-loop longhand would create drastically different transformation to for-loop longhand would create drastically different
semantics; the meaning of ``with`` inside a comprehension would be semantics; the meaning of ``with`` inside a comprehension would be
completely different from the meaning as a stand-alone statement. completely different from the meaning as a stand-alone statement, while
retaining identical syntax.
4. ``EXPR as NAME`` without parentheses:: 4. ``EXPR as NAME`` without parentheses::
stuff = [[f(x) as y, x/y] for x in range(5)] stuff = [[f(x) as y, x/y] for x in range(5)]
Omitting the parentheses from this PEP's proposed syntax introduces many Omitting the parentheses from this PEP's proposed syntax introduces many
syntactic ambiguities. syntactic ambiguities. Requiring them in all contexts leaves open the
option to make them optional in specific situations where the syntax is
unambiguous (cf generator expressions as sole parameters in function
calls), but there is no plausible way to make them optional everywhere.
5. Adorning statement-local names with a leading dot:: 5. Adorning statement-local names with a leading dot::
@ -303,6 +319,23 @@ which have been rejected in favour of the one given above.
This is exactly the same as the promoted proposal, save that the name is This is exactly the same as the promoted proposal, save that the name is
bound in the same scope that it would otherwise have. Any expression can bound in the same scope that it would otherwise have. Any expression can
assign to any name, just as it would if the ``=`` operator had been used. assign to any name, just as it would if the ``=`` operator had been used.
Such variables would leak out of the statement into the enclosing function,
subject to the regular behaviour of comprehensions (since they implicitly
create a nested function, the name binding would be restricted to the
comprehension itself, just as with the names bound by ``for`` loops).
7. Enhancing ``if`` and ``while`` syntax to permit the capture of their
conditions::
if re.search(pat, text) as match:
print("Found:", match.group(0))
This works beautifully if and ONLY if the desired condition is based on the
truthiness of the captured value. It is thus effective for specific
use-cases (regex matches, socket reads that return `''` when done), and
completely useless in more complicated cases (eg where the condition is
``f(x) < 0`` and you want to capture the value of ``f(x)``). It also has
no benefit to list comprehensions.
Discrepancies in the current implementation Discrepancies in the current implementation
@ -313,6 +346,8 @@ Discrepancies in the current implementation
their simple or mangled names (but buggily and unreliably). They should their simple or mangled names (but buggily and unreliably). They should
be suppressed as though they were guinea pigs. be suppressed as though they were guinea pigs.
2. The forbidden special cases do not yet raise SyntaxError.
References References
========== ==========