PEP 572: Update based on feedback from python-ideas
This commit is contained in:
parent
b8f3b3d108
commit
cc611e43b8
123
pep-0572.rst
123
pep-0572.rst
|
@ -43,11 +43,16 @@ The value of such a named expression is the same as the incorporated
|
|||
expression, with the additional side-effect that the target is assigned
|
||||
that value.
|
||||
|
||||
# Similar to the boolean 'or' but checking for None specifically
|
||||
x = "default" if (eggs := spam().ham) is None else eggs
|
||||
# Handle a matched regex
|
||||
if (match := pattern.search(data)) is not None:
|
||||
...
|
||||
|
||||
# Even complex expressions can be built up piece by piece
|
||||
y = ((eggs := spam()), (cheese := eggs.method()), cheese[eggs])
|
||||
# A more explicit alternative to the 2-arg form of iter() invocation
|
||||
while (value := read_next_item()) is not None:
|
||||
...
|
||||
|
||||
# Share a subexpression between a comprehension filter clause and its output
|
||||
filtered_data = [y for x in data if (y := f(x)) is not None]
|
||||
|
||||
|
||||
Differences from regular assignment statements
|
||||
|
@ -171,6 +176,11 @@ Simplifying list comprehensions
|
|||
|
||||
These list comprehensions are all approximately equivalent::
|
||||
|
||||
stuff = [[y := f(x), x/y] for x in range(5)]
|
||||
|
||||
# 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)]
|
||||
|
||||
|
@ -204,15 +214,17 @@ These list comprehensions are all approximately equivalent::
|
|||
c = {}
|
||||
stuff = [[c.update(y=f(x)) or c['y'], x/c['y']] for x in range(5)]
|
||||
|
||||
# Using a temporary name
|
||||
stuff = [[y := f(x), x/y] for x in range(5)]
|
||||
|
||||
If calling ``f(x)`` is expensive or has side effects, the clean operation of
|
||||
the list comprehension gets muddled. Using a short-duration name binding
|
||||
retains the simplicity; while the extra ``for`` loop does achieve this, it
|
||||
does so at the cost of dividing the expression visually, putting the named
|
||||
part at the end of the comprehension instead of the beginning.
|
||||
|
||||
Similarly, a list comprehension can map and filter efficiently by capturing
|
||||
the condition::
|
||||
|
||||
results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]
|
||||
|
||||
|
||||
Capturing condition values
|
||||
--------------------------
|
||||
|
@ -220,17 +232,6 @@ Capturing condition values
|
|||
Assignment expressions can be used to good effect in the header of
|
||||
an ``if`` or ``while`` statement::
|
||||
|
||||
# Current Python, not caring about function return value
|
||||
while input("> ") != "quit":
|
||||
print("You entered a command.")
|
||||
|
||||
# Current Python, capturing return value - four-line loop header
|
||||
while True:
|
||||
command = input("> ");
|
||||
if command == "quit":
|
||||
break
|
||||
print("You entered:", command)
|
||||
|
||||
# Proposed alternative to the above
|
||||
while (command := input("> ")) != "quit":
|
||||
print("You entered:", command)
|
||||
|
@ -245,6 +246,18 @@ an ``if`` or ``while`` statement::
|
|||
while data := sock.read():
|
||||
print("Received data:", data)
|
||||
|
||||
# Equivalent in current Python, not caring about function return value
|
||||
while input("> ") != "quit":
|
||||
print("You entered a command.")
|
||||
|
||||
# To capture the return value in current Python demands a four-line
|
||||
# loop header.
|
||||
while True:
|
||||
command = input("> ");
|
||||
if command == "quit":
|
||||
break
|
||||
print("You entered:", command)
|
||||
|
||||
Particularly with the ``while`` loop, this can remove the need to have an
|
||||
infinite loop, an assignment, and a condition. It also creates a smooth
|
||||
parallel between a loop which simply uses a function call as its condition,
|
||||
|
@ -471,6 +484,45 @@ common case where the iterable is pumped immediately (perhaps as part of a
|
|||
larger expression).
|
||||
|
||||
|
||||
Importing names into comprehensions
|
||||
-----------------------------------
|
||||
|
||||
A list comprehension can use and update local names, and they will retain
|
||||
their values from one iteration to another. It would be convenient to use
|
||||
this feature to create rolling or self-effecting data streams::
|
||||
|
||||
progressive_sums = [total := total + value for value in data]
|
||||
|
||||
This will fail with UnboundLocalError due to ``total`` not being initalized.
|
||||
Simply initializing it outside of the comprehension is insufficient - unless
|
||||
the comprehension is in class scope::
|
||||
|
||||
class X:
|
||||
total = 0
|
||||
progressive_sums = [total := total + value for value in data]
|
||||
|
||||
At other scopes, it may be beneficial to have a way to fetch a value from the
|
||||
surrounding scope. Should this be automatic? Should it be controlled with a
|
||||
keyword? Hypothetically (and using no new keywords), this could be written::
|
||||
|
||||
total = 0
|
||||
progressive_sums = [total := total + value
|
||||
import nonlocal total
|
||||
for value in data]
|
||||
|
||||
Translated into longhand, this would become::
|
||||
|
||||
total = 0
|
||||
def <listcomp>(total=total):
|
||||
result = []
|
||||
for value in data:
|
||||
result.append(total := total + value)
|
||||
return result
|
||||
progressive_sums = <listcomp>()
|
||||
|
||||
ie utilizing the same early-binding technique that is used at class scope.
|
||||
|
||||
|
||||
Frequently Raised Objections
|
||||
============================
|
||||
|
||||
|
@ -501,6 +553,41 @@ 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?
|
||||
-------------------------------------------------------------
|
||||
|
||||
Previous revisions of this proposal involved sublocal scope (restricted to a
|
||||
single statement), preventing name leakage and namespace pollution. While a
|
||||
definite advantage in a number of situations, this increases complexity in
|
||||
many others, and the costs are not justified by the benefits. In the interests
|
||||
of language simplicity, the name bindings created here are exactly equivalent
|
||||
to any other name bindings, including that usage at class or module scope will
|
||||
create externally-visible names. This is no different from ``for`` loops or
|
||||
other constructs, and can be solved the same way: ``del`` the name once it is
|
||||
no longer needed, or prefix it with an underscore.
|
||||
|
||||
Names bound within a comprehension are local to that comprehension, even in
|
||||
the outermost iterable, and can thus be used freely without polluting the
|
||||
surrounding namespace.
|
||||
|
||||
|
||||
Style guide recommendations
|
||||
===========================
|
||||
|
||||
As this adds another way to spell some of the same effects as can already be
|
||||
done, it is worth noting a few broad recommendations. These could be included
|
||||
in PEP 8 and/or other style guides.
|
||||
|
||||
1. If either assignment statements or assignment expressions can be
|
||||
used, prefer statements; they are a clear declaration of intent.
|
||||
|
||||
2. If using assignment expressions would lead to ambiguity about
|
||||
execution order, restructure it to use statements instead.
|
||||
|
||||
3. Chaining multiple assignment expressions should generally be avoided.
|
||||
More than one assignment per expression can detract from readability.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
|
|
Loading…
Reference in New Issue