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
|
expression, with the additional side-effect that the target is assigned
|
||||||
that value.
|
that value.
|
||||||
|
|
||||||
# Similar to the boolean 'or' but checking for None specifically
|
# Handle a matched regex
|
||||||
x = "default" if (eggs := spam().ham) is None else eggs
|
if (match := pattern.search(data)) is not None:
|
||||||
|
...
|
||||||
|
|
||||||
# Even complex expressions can be built up piece by piece
|
# A more explicit alternative to the 2-arg form of iter() invocation
|
||||||
y = ((eggs := spam()), (cheese := eggs.method()), cheese[eggs])
|
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
|
Differences from regular assignment statements
|
||||||
|
@ -171,6 +176,11 @@ Simplifying list comprehensions
|
||||||
|
|
||||||
These list comprehensions are all approximately equivalent::
|
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
|
# Calling the function twice
|
||||||
stuff = [[f(x), x/f(x)] for x in range(5)]
|
stuff = [[f(x), x/f(x)] for x in range(5)]
|
||||||
|
|
||||||
|
@ -204,15 +214,17 @@ These list comprehensions are all approximately equivalent::
|
||||||
c = {}
|
c = {}
|
||||||
stuff = [[c.update(y=f(x)) or c['y'], x/c['y']] for x in range(5)]
|
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
|
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
|
the list comprehension gets muddled. Using a short-duration name binding
|
||||||
retains the simplicity; while the extra ``for`` loop does achieve this, it
|
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
|
does so at the cost of dividing the expression visually, putting the named
|
||||||
part at the end of the comprehension instead of the beginning.
|
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
|
Capturing condition values
|
||||||
--------------------------
|
--------------------------
|
||||||
|
@ -220,17 +232,6 @@ Capturing condition values
|
||||||
Assignment expressions can be used to good effect in the header of
|
Assignment expressions can be used to good effect in the header of
|
||||||
an ``if`` or ``while`` statement::
|
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
|
# Proposed alternative to the above
|
||||||
while (command := input("> ")) != "quit":
|
while (command := input("> ")) != "quit":
|
||||||
print("You entered:", command)
|
print("You entered:", command)
|
||||||
|
@ -245,6 +246,18 @@ an ``if`` or ``while`` statement::
|
||||||
while data := sock.read():
|
while data := sock.read():
|
||||||
print("Received data:", data)
|
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
|
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
|
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,
|
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).
|
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
|
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.
|
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
|
Acknowledgements
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue