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
|
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
|
||||||
==========
|
==========
|
||||||
|
|
Loading…
Reference in New Issue