PEP 572 introduces assignment expressions (colloquially known as the Walrus Operator), implemented for Python 3.8. This seems like a really substantial new feature since it will allow this form of assignment within comprehensions and lambda functions.
What exactly are the syntax, semantics, and grammar specification of assignment expressions?
Why is this new (and seemingly quite radical concept) being introduced, when a similar idea in PEP 379 on "Adding an assignment expression" was rejected before?
PEP 572 contains many of the details, especially for the first question. I'll try to summarise/quote concisely arguably some of the most important parts of the PEP:
Rationale
Allowing this form of assignment within comprehensions, such as list comprehensions, and lambda functions where traditional assignments are forbidden. This can also facilitate interactive debugging without the need for code refactoring.
Recommended use-case examples
a) Getting conditional values
for example (in Python 3):
command = input("> ")
while command != "quit":
print("You entered:", command)
command = input("> ")
can become:
while (command := input("> ")) != "quit":
print("You entered:", command)
Similarly, from the docs:
In this example, the assignment expression helps avoid calling len()
twice:
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
b) Simplifying list comprehensions
for example:
stuff = [(lambda y: [y,x/y])(f(x)) for x in range(5)]
can become:
stuff = [[y := f(x), x/y] for x in range(5)]
Syntax and semantics
In any context where arbitrary Python expressions can be used, a named expression can appear. This is of the form name := expr where expr is any valid Python expression, and name is an identifier.
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
Differences from regular assignment statements
In addition to being an expression rather than statement, there are several differences mentioned in the PEP: expression assignments go right-to-left, have different priority around commas, and do not support:
Multiple targets
x = y = z = 0 # Equivalent: (z := (y := (x := 0)))
Assignments not to a single name:
# No equivalent
a[i] = x
self.rest = []
Iterable packing/unpacking
# Equivalent needs extra parentheses
loc = x, y # Use (loc := (x, y))
info = name, phone, *rest # Use (info := (name, phone, *rest))
# No equivalent
px, py, pz = position
name, phone, email, *other_info = contact
Inline type annotations:
# Closest equivalent is "p: Optional[int]" as a separate declaration
p: Optional[int] = None
Augmented assignment is not supported:
total += tax # Equivalent: (total := total + tax)
A couple of my favorite examples of where assignment expressions can make code more concise and easier to read:
if statement
Before:
match = pattern.match(line)
if match:
return match.group(1)
After:
if match := pattern.match(line):
return match.group(1)
Infinite while statement
Before:
while True:
data = f.read(1024)
if not data:
break
use(data)
After:
while data := f.read(1024):
use(data)
There are other good examples in the PEP.
A few more examples and rationales now that 3.8 has been officially released.
Naming the result of an expression is an important part of programming, allowing a descriptive name to be used in place of a longer expression, and permitting reuse. Currently, this feature is available only in statement form, making it unavailable in list comprehensions and other expression contexts.
Source: LicensedProfessional's reddit comment
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]
What is := operator?
In simple terms := is a expression + assignment operator.
it executes an expression and assigns the result of that expression in a single variable.
Why is := operator needed?
simple useful case will be to reduce function calls in comprehensions while maintaining the redability.
lets consider a list comprehension to add one and filter if result is grater than 0 without a := operator. Here we need to call the add_one function twice.
[add_one(num) for num in numbers if add_one(num) > 0]
Case 1:
def add_one(num):
return num + 1
numbers = [1,2,3,4,-2,45,6]
result1 = [value for num in numbers if (value := add_one(num)) > 0]
>>> result1
[2, 3, 4, 5, 46, 7]
The result is as expected and we don't need to call the add_one function to call twice which shows the advantage of := operator
be cautious with walarus := operator while using list comprehension
below cases might help you better understand the use of := operator
Case 2:
def add_one(num):
return num + 1
numbers = [1,2,3,4,-2,45,6]
>>> result2 = [(value := add_one(num)) for num in numbers if value > 0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
NameError: name 'value' is not defined
Case 3: when a global variable is set to positive
def add_one(num):
return num + 1
numbers = [1,2,3,4,-2,45,6]
value = 1
result3 = [(value := add_one(num)) for num in numbers if value > 0]
>>> result3
[2, 3, 4, 5, -1]
Case 4: when a global variable is set to negitive
def add_one(num):
return num + 1
numbers = [1,2,3,4,-2,45,6]
value = -1
result4 = [(value := add_one(num)) for num in numbers if value > 0]
>>> result4
[]
Related
This works just fine
x = 0
while True:
x += 1
print(x)
while this
x = 0
while True:
print(x += 1)
doesn't
I want a program that counts to infinity or at least until max digits
Unlike many other languages, where an assignment is an expression and evaluates to the assigned value, in Python an assignment is its own statement. Therefore it can't be used in an expression.
One advantage of this is that if you forget an = in an if statement (i.e. you meant to write == but wrote = instead) you get an error:
if a = b: # this is an assignment not a comparison! SyntaxError
In certain other languages this is valid syntactically but wouldn't give you the result you intend, causing hair-loss bugs. (This is one reason linters were invented. The language itself didn't prevent you from making this mistake, so they created an external tool to help with that.)
Python 3.8 adds the assignment operator, :=, a.k.a. the walrus operator. It behaves like assignment in other languages, although you still can't use it everywhere. So this works:
x = 0
while True:
print(x := x + 1)
Unfortunately (or fortunately) there is no +:=, which I guess you'd call an augmented walrus.
Because the argument to print() needs to be an expression, and an assignment statement is not an expression.
The walrus operator := was introduced in Python precisely to allow you to do this, though it does not have a variant which allows you to increment something. But you can say
x = 0
while True:
print(x := x + 1)
This does not strike me as a particularly good or idiomatic use of this operator, though.
Is it possible to have assignment in a condition?
For ex.
if (a=some_func()):
# Use a
Why not try it out?
>>> def some_func():
... return 2
...
>>> if (a = some_func()):
File "<stdin>", line 1
if (a = some_func()):
^
SyntaxError: invalid syntax
So, no.
Update: This is possible (with different syntax) in Python 3.8
if a := some_func():
UPDATE - Original answer is near the bottom
Python 3.8 will bring in PEP572
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.
https://lwn.net/Articles/757713/
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.
https://www.python.org/dev/peps/pep-0572/#examples-from-the-python-standard-library
Examples from the Python standard library
site.py env_base is only used on these lines, putting its assignment on the if moves it as the "header" of the block.
Current:
env_base = os.environ.get("PYTHONUSERBASE", None)
if env_base:
return env_base
Improved:
if env_base := os.environ.get("PYTHONUSERBASE", None):
return env_base
_pydecimal.py
Avoid nested if and remove one indentation level.
Current:
if self._is_special:
ans = self._check_nans(context=context)
if ans:
return ans
Improved:
if self._is_special and (ans := self._check_nans(context=context)):
return ans
copy.py Code looks more regular and avoid multiple nested if. (See Appendix A for the origin of this example.)
Current:
reductor = dispatch_table.get(cls)
if reductor:
rv = reductor(x)
else:
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(4)
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
else:
raise Error(
"un(deep)copyable object of type %s" % cls)
Improved:
if reductor := dispatch_table.get(cls):
rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
rv = reductor()
else:
raise Error("un(deep)copyable object of type %s" % cls)
datetime.py
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.
Fork An example from the low-level UNIX world:
if pid := os.fork():
# Parent code
else:
# Child code
Original answer
http://docs.python.org/tutorial/datastructures.html
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.
also see:
http://effbot.org/pyfaq/why-can-t-i-use-an-assignment-in-an-expression.htm
Nope, the BDFL didn't like that feature.
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.
Yes, but only from Python 3.8 and onwards.
PEP 572 proposes Assignment Expressions and has already been accepted.
Quoting the Syntax and semantics part of the PEP:
# 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]
In your specific case, you will be able to write
if a := some_func():
# Use a
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
No. Assignment in Python is a statement, not an expression.
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
You can define a function to do the assigning for you:
def assign(name, value):
import inspect
frame = inspect.currentframe()
try:
locals_ = frame.f_back.f_locals
finally:
del frame
locals_[name] = value
return value
if assign('test', 0):
print("first", test)
elif assign('xyz', 123):
print("second", xyz)
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.
The assignment operator - also known informally as the the walrus operator - was created at 28-Feb-2018 in PEP572.
For the sake of completeness, I'll post the relevant parts so you can compare the differences between 3.7 and 3.8:
3.7
---
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
test: or_test ['if' or_test 'else' test] | lambdef
test_nocond: or_test | lambdef_nocond
lambdef: 'lambda' [varargslist] ':' test
lambdef_nocond: 'lambda' [varargslist] ':' test_nocond
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*
3.8
---
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
namedexpr_test: test [':=' test] <---- WALRUS OPERATOR!!!
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*
I read in Twitter:
#Python news: Guido accepted PEP 572. Python now has assignment expressions.
if (match := (pattern.search) pattern.search(data)) is not None:
print((match.group) mo.group(1))
filtered_data = [y for x in data if (y := f(x)) is not None]
(correction mine in the 2nd line of code)
As indicated, PEP 572 -- Assignment Expressions describes this to be present in Python 3.8:
This is a proposal for creating a way to assign to variables within an expression using the notation NAME := expr.
I've gone through the description and examples and I see this is a convenient way to avoid repetitions of either calls or assignments, so instead of:
match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
return match1.group(1)
elif match2:
return match2.group(2)
or the more efficient:
match1 = pattern1.match(data)
if match1:
return match1.group(1)
else:
match2 = pattern2.match(data)
if match2:
return match2.group(2)
One now can say:
if match1 := pattern1.match(data):
return match1.group(1)
elif match2 := pattern2.match(data):
return match2.group(2)
Similarly, one can now say:
if any(len(longline := line) >= 100 for line in lines):
print("Extremely long line:", longline)
However, I do not understand how this example given in the PEP is not valid:
y0 = y1 := f(x) # INVALID
Will it be correct to say y0 = (y1 := f(x))? How could it be used?
Foot note for those who wonder where will this be available: I already installed Python 3.7 and it does not work there, since the PEP currently shows as "Status: Draft". However, the PEP talks about Proof of concept / reference implementation (https://github.com/Rosuav/cpython/tree/assignment-expressions), so it is a matter of using their Python 3.8 alpha 0 version that includes it.
As explicitly stated in the PEP,
Unparenthesized assignment expressions are prohibited at the top level in the right hand side of an assignment statement; for example, the following is not allowed:
y0 = y1 := f(x) # INVALID
Again, this rule is included to avoid two visually similar ways of saying the same thing.
And later,
As follows from section "Exceptional cases" above, it is never allowed at the same level as =. In case a different grouping is desired, parentheses should be used.
...
# INVALID
x = y := 0
# Valid alternative
x = (y := 0)
I would like to have a function AllTrue that takes three arguments:
List: a list of values
Function: a function to apply to all values
Condition: something to test against the function's output
and return a boolean of whether or not all values in the list match the criteria.
I can get this to work for basic conditions as follows:
def AllTrue(List, Function = "Boolean", Condition = True):
flag = True
condition = Condition
if Function == "Boolean"
for element in List:
if element != condition:
flag = False
break
else:
Map = map(Function, List)
for m in Map:
if m != condition:
flag = False
break
return flag
Since python doesn't have function meant for explicitly returning if something is True, I just make the default "Boolean". One could clean this up by defining TrueQ to return True if an element is True and then just mapping TrueQ on the List.
The else handles queries like:
l = [[0,1], [2,3,4,5], [6,7], [8,9],[10]]
AllTrue(l, len, 2)
#False
testing if all elements in the list are of length 2. However, it can't handle more complex conditions like >/< or compound conditions like len > 2 and element[0] == 15
How can one do this?
Cleaned up version
def TrueQ(item):
return item == True
def AllTrue(List, Function = TrueQ, Condition = True):
flag = True
condition = Condition
Map = map(Function, List)
for m in Map:
if m != condition:
flag = False
break
return flag
and then just call AllTrue(List,TrueQ)
Python already has built-in the machinery you are trying to build. For example to check if all numbers in a list are even the code could be:
if all(x%2==0 for x in L):
...
if you want to check that all values are "truthy" the code is even simpler:
if all(L):
...
Note that in the first version the code is also "short-circuited", in other words the evaluation stops as soon as the result is known. In:
if all(price(x) > 100 for x in stocks):
...
the function price will be called until the first stock is found with a lower or equal price value. At that point the search will stop because the result is known to be False.
To check that all lengths are 2 in the list L the code is simply:
if all(len(x) == 2 for x in L):
...
i.e. more or less a literal translation of the request. No need to write a function for that.
If this kind of test is a "filter" that you want to pass as a parameter to another function then a lambda may turn out useful:
def search_DB(test):
for record in database:
if test(record):
result.append(record)
...
search_DB(lambda rec: all(len(x) == 2 for x in rec.strings))
I want a function that takes a list, a function, and a condition, and tells me if every element in the list matches the condition. i.e. foo(List, Len, >2)
In Python >2 is written lambda x : x>2.
There is (unfortunately) no metaprogramming facility in Python that would allow to write just >2 or things like ·>2 except using a string literal evaluation with eval and you don't want to do that. Even the standard Python library tried going down that path (see namedtuple implementation in collections) but it's really ugly.
I'm not saying that writing >2 would be a good idea, but that it would be nice to have a way to do that in case it was a good idea. Unfortunately to have decent metaprogramming abilities you need a homoiconic language representing code as data and therefore you would be programming in Lisp or another meta-language, not Python (programming in Lisp would indeed be a good idea, but for reasons unknown to me that approach is still unpopular).
Given that, the function foo to be called like
foo(L, len, lambda x : x > 2)
is just
def foo(L, f=lambda x : x, condition=lambda x: x):
return all(condition(f(x)) for x in L)
but no Python programmer would write such a function, because the original call to foo is actually more code and less clear than inlining it with:
all(len(x) > 2 for x in L)
and requires you to also learn about this thing foo (that does what all and a generator expression would do, just slower, with more code and more obfuscated).
You are reinventing the wheel. Just use something like this:
>>> l = [[0,1], [2,3,4,5], [6,7], [8,9],[10]]
>>> def all_true(iterable, f, condition):
... return all(condition(f(e)) for e in iterable)
...
>>> def cond(x): return x == 2
...
>>> all_true(l, len, cond)
False
You can define a different function to check a different condition:
>>> def cond(x): return x >= 1
...
>>> all_true(l, len, b)
True
>>>
And really, having your own function that does this seems like overkill. For example, to deal with your "complex condition" you could simply do something like:
>>> l = [[0,2],[0,1,2],[0,1,3,4]]
>>> all(len(sub) > 2 and sub[0] == 5 for sub in l)
False
>>> all(len(sub) > 1 and sub[0] == 0 for sub in l)
True
>>>
I think the ideal solution in this case may be:
def AllTrue(List, Test = lambda x:x):
all(Test(x) for x in List)
This thereby allows complex queries like:
l = [[0, 1], [1, 2, 3], [2, 5]]
AllTrue(l, lambda x: len(x) > 2 and x[0] == 1)
To adhere to Juanpa's suggestion, here it is in python naming conventions and an extension of what I posted in the question now with the ability to handle simple conditions like x > value.
from operator import *
all_true(a_list, a_function, an_operator, a_value):
a_map = map(a_function, a_list)
return all( an_operator(m, a_value) for m in a_map)
l = [[0,2],[0,1,2],[0,1,3,4]]
all_true(l, len, gt, 2)
#True
Note: this works for single conditions, but not for complex conditions like
len > 2 and element[0] == 5
Is it possible to have assignment in a condition?
For ex.
if (a=some_func()):
# Use a
Why not try it out?
>>> def some_func():
... return 2
...
>>> if (a = some_func()):
File "<stdin>", line 1
if (a = some_func()):
^
SyntaxError: invalid syntax
So, no.
Update: This is possible (with different syntax) in Python 3.8
if a := some_func():
UPDATE - Original answer is near the bottom
Python 3.8 will bring in PEP572
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.
https://lwn.net/Articles/757713/
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.
https://www.python.org/dev/peps/pep-0572/#examples-from-the-python-standard-library
Examples from the Python standard library
site.py env_base is only used on these lines, putting its assignment on the if moves it as the "header" of the block.
Current:
env_base = os.environ.get("PYTHONUSERBASE", None)
if env_base:
return env_base
Improved:
if env_base := os.environ.get("PYTHONUSERBASE", None):
return env_base
_pydecimal.py
Avoid nested if and remove one indentation level.
Current:
if self._is_special:
ans = self._check_nans(context=context)
if ans:
return ans
Improved:
if self._is_special and (ans := self._check_nans(context=context)):
return ans
copy.py Code looks more regular and avoid multiple nested if. (See Appendix A for the origin of this example.)
Current:
reductor = dispatch_table.get(cls)
if reductor:
rv = reductor(x)
else:
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(4)
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
else:
raise Error(
"un(deep)copyable object of type %s" % cls)
Improved:
if reductor := dispatch_table.get(cls):
rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
rv = reductor()
else:
raise Error("un(deep)copyable object of type %s" % cls)
datetime.py
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.
Fork An example from the low-level UNIX world:
if pid := os.fork():
# Parent code
else:
# Child code
Original answer
http://docs.python.org/tutorial/datastructures.html
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.
also see:
http://effbot.org/pyfaq/why-can-t-i-use-an-assignment-in-an-expression.htm
Nope, the BDFL didn't like that feature.
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.
Yes, but only from Python 3.8 and onwards.
PEP 572 proposes Assignment Expressions and has already been accepted.
Quoting the Syntax and semantics part of the PEP:
# 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]
In your specific case, you will be able to write
if a := some_func():
# Use a
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
No. Assignment in Python is a statement, not an expression.
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
You can define a function to do the assigning for you:
def assign(name, value):
import inspect
frame = inspect.currentframe()
try:
locals_ = frame.f_back.f_locals
finally:
del frame
locals_[name] = value
return value
if assign('test', 0):
print("first", test)
elif assign('xyz', 123):
print("second", xyz)
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.
The assignment operator - also known informally as the the walrus operator - was created at 28-Feb-2018 in PEP572.
For the sake of completeness, I'll post the relevant parts so you can compare the differences between 3.7 and 3.8:
3.7
---
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
test: or_test ['if' or_test 'else' test] | lambdef
test_nocond: or_test | lambdef_nocond
lambdef: 'lambda' [varargslist] ':' test
lambdef_nocond: 'lambda' [varargslist] ':' test_nocond
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*
3.8
---
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
namedexpr_test: test [':=' test] <---- WALRUS OPERATOR!!!
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*