PEP 572: Final updates prior to posting to python-dev
This commit is contained in:
parent
1129ab3aa3
commit
73715149df
91
pep-0572.rst
91
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, 23-Mar-2018, 04-Apr-2018
|
||||
Post-History: 28-Feb-2018, 02-Mar-2018, 23-Mar-2018, 04-Apr-2018, 17-Apr-2018
|
||||
|
||||
|
||||
Abstract
|
||||
|
@ -37,7 +37,7 @@ Syntax and semantics
|
|||
In any context where arbitrary Python expressions can be used, a **named
|
||||
expression** can appear. This is of the form ``target := expr`` where
|
||||
``expr`` is any valid Python expression, and ``target`` is any valid
|
||||
assignment target. (NOTE: See 'Open questions' below for precedence.)
|
||||
assignment target.
|
||||
|
||||
The value of such a named expression is the same as the incorporated
|
||||
expression, with the additional side-effect that the target is assigned
|
||||
|
@ -61,11 +61,12 @@ Differences from regular assignment statements
|
|||
Most importantly, since ``:=`` is an expression, it can be used in contexts
|
||||
where statements are illegal, including lambda functions and comprehensions.
|
||||
|
||||
An assignment statement can assign to multiple targets::
|
||||
An assignment statement can assign to multiple targets, left-to-right::
|
||||
|
||||
x = y = z = 0
|
||||
|
||||
To do the same with assignment expressions, they must be parenthesized::
|
||||
The equivalent assignment expression is parsed as separate binary operators,
|
||||
and is therefore processed right-to-left, as if it were spelled thus::
|
||||
|
||||
assert 0 == (x := (y := (z := 0)))
|
||||
|
||||
|
@ -77,7 +78,8 @@ Augmented assignment is not supported in expression form::
|
|||
^
|
||||
SyntaxError: invalid syntax
|
||||
|
||||
Otherwise, the semantics of assignment are unchanged by this proposal.
|
||||
Otherwise, the semantics of assignment are identical in statement and
|
||||
expression forms.
|
||||
|
||||
|
||||
Alterations to comprehensions
|
||||
|
@ -184,9 +186,6 @@ These list comprehensions are all approximately equivalent::
|
|||
# There are a number of less obvious ways to spell this in current
|
||||
# versions of Python.
|
||||
|
||||
# Calling the function twice
|
||||
stuff = [[f(x), x/f(x)] for x in range(5)]
|
||||
|
||||
# External helper function
|
||||
def pair(x, value): return [value, x/value]
|
||||
stuff = [pair(x, f(x)) for x in range(5)]
|
||||
|
@ -410,6 +409,31 @@ in a statement; alternatively, a new keyword is needed, with all the costs
|
|||
therein.
|
||||
|
||||
|
||||
Lowering operator precedence
|
||||
----------------------------
|
||||
|
||||
There are two logical precedences for the ``:=`` operator. Either it should
|
||||
bind as loosely as possible, as does statement-assignment; or it should bind
|
||||
more tightly than comparison operators. Placing its precedence between the
|
||||
comparison and arithmetic operators (to be precise: just lower than bitwise
|
||||
OR) allows most uses inside ``while`` and ``if`` conditions to be spelled
|
||||
without parentheses, as it is most likely that you wish to capture the value
|
||||
of something, then perform a comparison on it::
|
||||
|
||||
pos = -1
|
||||
while pos := buffer.find(search_term, pos + 1) >= 0:
|
||||
...
|
||||
|
||||
Once find() returns -1, the loop terminates. If ``:=`` binds as loosely as
|
||||
``=`` does, this would capture the result of the comparison (generally either
|
||||
``True`` or ``False``), which is less useful.
|
||||
|
||||
While this behaviour would be convenient in many situations, it is also harder
|
||||
to explain than "the := operator behaves just like the assignment statement",
|
||||
and as such, the precedence for ``:=`` has been made as close as possible to
|
||||
that of ``=``.
|
||||
|
||||
|
||||
Migration path
|
||||
==============
|
||||
|
||||
|
@ -440,6 +464,26 @@ to yield values (and is thus a generator function). The entire comprehension
|
|||
is consistently in a single scope.
|
||||
|
||||
|
||||
Name reuse inside comprehensions
|
||||
--------------------------------
|
||||
|
||||
If the same name is used in the outermost iterable and also as an iteration
|
||||
variable, this will now raise UnboundLocalError when previously it referred
|
||||
to the name in the surrounding scope. Example::
|
||||
|
||||
# Lib/typing.py
|
||||
tvars = []
|
||||
for t in types:
|
||||
if isinstance(t, TypeVar) and t not in tvars:
|
||||
tvars.append(t)
|
||||
if isinstance(t, _GenericAlias) and not t._special:
|
||||
tvars.extend([ty for ty in t.__parameters__ if ty not in tvars])
|
||||
|
||||
If the list comprehension uses the name ``t`` rather than ``ty``, this will
|
||||
work in Python 3.7 but not with this proposal. As with other unwanted name
|
||||
shadowing, the solution is to use distinct names.
|
||||
|
||||
|
||||
Name lookups in class scope
|
||||
---------------------------
|
||||
|
||||
|
@ -477,7 +521,9 @@ function form::
|
|||
gen = <genexp>() # No exception yet
|
||||
tng = next(gen) # NameError
|
||||
|
||||
To detect these errors more quickly, ... TODO.
|
||||
Detecting these errors more quickly is nontrivial. It is, however, the exact
|
||||
same problem as generator functions currently suffer from, and this proposal
|
||||
brings the genexp in line with the most natural longhand form.
|
||||
|
||||
|
||||
Open questions
|
||||
|
@ -548,26 +594,6 @@ Translated into longhand, this would become::
|
|||
ie utilizing the same early-binding technique that is used at class scope.
|
||||
|
||||
|
||||
Operator precedence
|
||||
-------------------
|
||||
|
||||
There are two logical precedences for the ``:=`` operator. Either it should
|
||||
bind as loosely as possible, as does statement-assignment; or it should bind
|
||||
more tightly than comparison operators. Placing its precedence between the
|
||||
comparison and arithmetic operators (to be precise: just lower than bitwise
|
||||
OR) allows most uses inside ``while`` and ``if`` conditions to be spelled
|
||||
without parentheses, as it is most likely that you wish to capture the value
|
||||
of something, then perform a comparison on it::
|
||||
|
||||
pos = -1
|
||||
while pos := buffer.find(search_term, pos + 1) >= 0:
|
||||
...
|
||||
|
||||
Once find() returns -1, the loop terminates. If ``:=`` binds as loosely as
|
||||
``=`` does, this would capture the result of the comparison (generally either
|
||||
``True`` or ``False``), which is less useful.
|
||||
|
||||
|
||||
Frequently Raised Objections
|
||||
============================
|
||||
|
||||
|
@ -592,10 +618,9 @@ With assignment expressions, why bother with assignment statements?
|
|||
-------------------------------------------------------------------
|
||||
|
||||
The two forms have different flexibilities. The ``:=`` operator can be used
|
||||
inside a larger expression; the ``=`` operator can be chained more
|
||||
conveniently, and closely parallels the inline operations ``+=`` and friends.
|
||||
The assignment statement is a clear declaration of intent: this value is to
|
||||
be assigned to this target, and that's it.
|
||||
inside a larger expression; the ``=`` statement can be augmented to ``+=`` and
|
||||
its friends. The assignment statement is a clear declaration of intent: this
|
||||
value is to be assigned to this target, and that's it.
|
||||
|
||||
|
||||
Why not use a sublocal scope and prevent namespace pollution?
|
||||
|
|
Loading…
Reference in New Issue