":=" syntax and assignment expressions: what and why?
Solution 1:
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
whereexpr
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)
Solution 2:
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.
Solution 3:
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]