Abstract
This is a proposal for creating a way to assign to variables
within an expression using the notation NAME := expr. A new exception,
TargetScopeError is added, and there is one change to evaluation
order.
The "PEP 572 mess" was the topic of a 2018 Python Language Summit
session led by benevolent dictator for life (BDFL) Guido van Rossum.
PEP 572 seeks to add assignment expressions (or "inline assignments")
to the language, but it has seen a prolonged discussion over multiple
huge threads on the python-dev mailing list—even after multiple rounds
on python-ideas. Those threads were often contentious and were clearly
voluminous to the point where many probably just tuned them out. At
the summit, Van Rossum gave an overview of the feature proposal, which
he seems inclined toward accepting, but he also wanted to discuss how
to avoid this kind of thread explosion in the future.
tz is only used for s += tz, moving its assignment inside the if helps
to show its scope.
Current:
s = _format_time(self._hour, self._minute,
self._second, self._microsecond,
timespec)
tz = self._tzstr()
if tz:
s += tz
return s
Improved:
s = _format_time(self._hour, self._minute,
self._second, self._microsecond,
timespec)
if tz := self._tzstr():
s += tz
return s
sysconfig.py Calling fp.readline() in the while condition and calling .match() on the if lines make the code more compact without
making it harder to understand.
Current:
while True:
line = fp.readline()
if not line:
break
m = define_rx.match(line)
if m:
n, v = m.group(1, 2)
try:
v = int(v)
except ValueError:
pass
vars[n] = v
else:
m = undef_rx.match(line)
if m:
vars[m.group(1)] = 0
Improved:
while line := fp.readline():
if m := define_rx.match(line):
n, v = m.group(1, 2)
try:
v = int(v)
except ValueError:
pass
vars[n] = v
elif m := undef_rx.match(line):
vars[m.group(1)] = 0
Simplifying list comprehensions 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]
Similarly, a subexpression can be reused within the main expression,
by giving it a name on first use:
stuff = [[y := f(x), x/y] for x in range(5)]
Note that in both cases the variable y is bound in the containing
scope (i.e. at the same level as results or stuff).
Capturing condition values Assignment expressions can be used to good effect in the header of an if or while statement:
# Loop-and-a-half
while (command := input("> ")) != "quit":
print("You entered:", command)
# Capturing regular expression match objects
# See, for instance, Lib/pydoc.py, which uses a multiline spelling
# of this effect
if match := re.search(pat, text):
print("Found:", match.group(0))
# The same syntax chains nicely into 'elif' statements, unlike the
# equivalent using assignment statements.
elif match := re.search(otherpat, text):
print("Alternate found:", match.group(0))
elif match := re.search(third, text):
print("Fallback found:", match.group(0))
# Reading socket data until an empty string is returned
while data := sock.recv(8192):
print("Received data:", data)
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, and one which uses that as its condition but also uses
the actual value.
Note that in Python, unlike C,
assignment cannot occur inside
expressions. C programmers may grumble
about this, but it avoids a common
class of problems encountered in C
programs: typing = in an expression
when == was intended.
From where I sit, Guido van Rossum, "Benevolent Dictator For Life”, has fought hard to keep Python as simple as it can be. We can quibble with some of the decisions he's made -- I'd have preferred he said 'No' more often. But the fact that there hasn't been a committee designing Python, but instead a trusted "advisory board", based largely on merit, filtering through one designer's sensibilities, has produced one hell of a nice language, IMHO.
Not directly, per this old recipe of mine -- but as the recipe says it's easy to build the semantic equivalent, e.g. if you need to transliterate directly from a C-coded reference algorithm (before refactoring to more-idiomatic Python, of course;-). I.e.:
class DataHolder(object):
def __init__(self, value=None): self.value = value
def set(self, value): self.value = value; return value
def get(self): return self.value
data = DataHolder()
while data.set(somefunc()):
a = data.get()
# use a
BTW, a very idiomatic Pythonic form for your specific case, if you know exactly what falsish value somefunc may return when it does return a falsish value (e.g. 0), is
for a in iter(somefunc, 0):
# use a
so in this specific case the refactoring would be pretty easy;-).
If the return could be any kind of falsish value (0, None, '', ...), one possibility is:
import itertools
for a in itertools.takewhile(lambda x: x, iter(somefunc, object())):
# use a
but you might prefer a simple custom generator:
def getwhile(func, *a, **k):
while True:
x = func(*a, **k)
if not x: break
yield x
for a in getwhile(somefunc):
# use a
One of the reasons why assignments are illegal in conditions is that it's easier to make a mistake and assign True or False:
some_variable = 5
# This does not work
# if True = some_variable:
# do_something()
# This only works in Python 2.x
True = some_variable
print True # returns 5
In Python 3 True and False are keywords, so no risk anymore.
Thanks to Python 3.8 new feature it will be possible to do such a thing from this version, although not using = but Ada-like assignment operator :=. Example from the docs:
# Handle a matched regex
if (match := pattern.search(data)) is not None:
# Do something with match
# Handle a matched regex
if (match := pattern.search(data)) is not None:
# Do something with match
# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
process(chunk)
# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]
# 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]