PEP 572: Update based on feedback from python-ideas

This commit is contained in:
Chris Angelico 2018-04-12 23:39:05 +10:00
parent b8f3b3d108
commit cc611e43b8
1 changed files with 105 additions and 18 deletions

View File

@ -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
================