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
Created: 28-Feb-2018
Python-Version: 3.8
Post-History: 28-Feb-2018, 02-Mar-2018
Post-History: 28-Feb-2018, 02-Mar-2018, 23-Mar-2018
Abstract
@ -36,19 +36,19 @@ actual value available within the loop body.
Syntax and semantics
====================
In any context where arbitrary Python expressions can be used, a named
expression can appear. This must be parenthesized for clarity, and is of
In any context where arbitrary Python expressions can be used, a **named
expression** can appear. This must be parenthesized for clarity, and is of
the form ``(expr as NAME)`` where ``expr`` is any valid Python expression,
and ``NAME`` is a simple name.
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
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
function, statement-local names shadow other names for that statement.
They can also shadow each other, though actually doing this should be
strongly discouraged in style guides.
(They can technically also shadow each other, though actually doing this
should not be encouraged.)
Assignment to statement-local names is ONLY through this syntax. Regular
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
print("Running command", cmd) # Works
Some of these examples should be considered *bad code* and rejected by code
review and/or linters; they are not, however, illegal.
Function bodies, in this respect, behave the same way they do in class scope;
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
@ -110,6 +112,11 @@ important distinctions.
* 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.
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
=============
@ -126,7 +133,7 @@ These list comprehensions are all approximately equivalent::
# Inline helper function
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)]]
# Iterating over a genexp
@ -143,7 +150,7 @@ These list comprehensions are all approximately equivalent::
for x in range(5):
y = f(x)
yield [y, x/y]
stuff = list(g)
stuff = list(g())
# Using a statement-local name
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.
Open questions
==============
Forbidden special cases
=======================
1. What happens if the name has already been used? ``(x, (1 as x), x)``
Currently, prior usage functions as if the named expression did not
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?
In two situations, the use of SLNBs makes no sense, and could be confusing due
to the ``as`` keyword already having a different meaning in the same context.
2. Syntactic confusion in ``except`` statements. While technically
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``.
1. Exception catching::
3. Similar confusion in ``with`` statements, with the difference that there
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,
``with expr as name:`` will do the same thing as ``with (expr as name):``,
adding to the confusion.
try:
...
except (Exception as e1) as e2:
...
4. Should closures be able to refer to statement-local names? Either way,
there will be edge cases that make no sense. Assigning to a name will
"push through" the SLNB and bind to the regular name; this means that a
statement ``x = x`` will promote the SLNB to full name, and thus has an
impact. Closing over statement-local names, however, introduces scope
and lifetime confusions, as it then becomes possible to have two functions
in almost the same context, closing over the same name, referring to two
different cells.
The expression ``(Exception as e1)`` has the value ``Exception``, and
creates an SLNB ``e1 = Exception``. This is generally useless, and creates
the potential confusion in that these two statements do quite different
things:
except (Exception as e1):
except Exception as e2:
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
=====================
Proposals of this nature have come up frequently on python-ideas. Below are
a number of alternative syntaxes, some of them specific to comprehensions,
which have been rejected in favour of the one given above.
Proposals broadly similar to this one have come up frequently on python-ideas.
Below are a number of alternative syntaxes, some of them specific to
comprehensions, which have been rejected in favour of the one given above.
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``
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)]
@ -268,24 +279,29 @@ which have been rejected in favour of the one given above.
no additional language keyword. Is restricted to comprehensions, though,
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
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)]
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
transformation to for-loop longhand would create drastically different
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::
stuff = [[f(x) as y, x/y] for x in range(5)]
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::
@ -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
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.
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
@ -313,6 +346,8 @@ Discrepancies in the current implementation
their simple or mangled names (but buggily and unreliably). They should
be suppressed as though they were guinea pigs.
2. The forbidden special cases do not yet raise SyntaxError.
References
==========