What are assignment expressions (using the "walrus" or ":=" operator)? Why was this syntax added?
Asked Answered
S

6

109

Since Python 3.8, code can use the so-called "walrus" operator (:=), documented in PEP 572, for assignment expressions.

This seems like a really substantial new feature, since it allows this form of assignment within comprehensions and lambdas.

What exactly are the syntax, semantics, and grammar specifications of assignment expressions?

Why was this new (and seemingly quite radical) concept introduced, even though PEP 379 (which proposes the similar idea of "Adding an assignment expression") was withdrawn?

Squire answered 11/5, 2018 at 17:50 Comment(5)
Are there organically-asked questions on this topic that can be closed with a link to this reference question? A question that might otherwise be approaching "too broad" can certainly be justifiable when it addresses what is otherwise a source of common duplicates.Parik
This should be reopened. This is definitely not "too broad". It's a very specific subject and a very good reference question.Zamudio
While it shouldn't be taken too literally because I'm sure Python may diverge in some ways, this is one of Go's best features and there are examples throughout the Go docsDiastole
Just to give you a historical perspective: A long and heated discussion among Python developers preceded the approval of PEP 572. And it appears to be one of the reasons why Guido resigned as BDFL. The assignment expressions have a number of valid usecases but can also be easily misused to make code less readable. Try to limit use of the walrus operator to clean cases that reduce complexity and improve readability.Poi
For what it's worth: the earliest drafts of PEP 572 proposed the same kind of as syntax as in PEP 379. Although there was a lot of discussion and a huge amount of work done on the PEP, it was only about four and a half months from proposal to acceptance - some other PEPs have taken years.Aronoff
S
97

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:

[(lambda y: [y, x/y])(x+1) for x in range(5)]

can become:

[[y := x+1, 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)
    
Squire answered 11/5, 2018 at 17:50 Comment(1)
You did not answer the most interesting part of your question: why was PEP 379 rejected and what is the difference between PEP 379 and PEP 572?Mayer
T
60

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.

Tourism answered 22/5, 2019 at 6:41 Comment(1)
Especially good examples: they show an aspect under which C sometimes managed to be clearer and more elegant than Python, I thought they were never going to fix thisAkvavit
L
12

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]
Luellaluelle answered 15/10, 2019 at 14:14 Comment(1)
does the (y := f(x)) have to be placed in the comprehension filter clause or can it be put in th output? Edit: just tested it and it works, i.e. you can do [(y := f(x)) for x in data if y is not None]Curriery
S
3

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
[]
Sonstrom answered 29/3, 2022 at 4:39 Comment(0)
M
0

Walrus operator can be used to avoid recomputation of some functions. Here is an example.

case 1:
if complexOperation(num) > 90:
  result = complexOperation(num)
  executeActualFunctionality(result)

case 2:
if result := complexOperation(num) > 90:
  executeActualFunctionality(result)

In case 1, we are performing complexOperation() twice just because we are not sure of the result. This can be avoided by practicing case 2.

Note: we can also declare result variable before "IF loop" and compute complexOperation() once. But Python is all about decreasing number of lines

Mcnair answered 11/8, 2023 at 10:51 Comment(0)
D
-1
# WALRUS OPERATOR :
Without walrus operator :
            card_number =input("Enter card number")
            if len(card_number)==8:
            print ("Card is valid")
            else:
            print("card is invalid")

With walrus operator :
            print ("card is valid") if len (card_number :=input("Enter card number"))==8 else print("card is valid,Enter card number again")
Discountenance answered 1/7, 2023 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.