PEP 572: Reword and update in response to mailing list changes
This commit is contained in:
parent
d43b984e02
commit
63be12066a
133
pep-0572.rst
133
pep-0572.rst
|
@ -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
|
||||
==========
|
||||
|
|
Loading…
Reference in New Issue