Some practical use cases for the walrus operator¶
I remember there was a controversy around the inclusion of the walrus operator (denoted by :=
and detailed here) in Python 3.7. However, I never really looked into when you would actually use it.
Recently, I was writing some code that performed some duplicate operations in a list comprehension and searched for a way to avoid duplication. The solution: the walrus operator!
Here's a list of tokens, each (for some reason) has ended up with a space either side.
tokens = [
" hello ",
" ",
" world ",
" ! ",
" how ",
" ",
" are ",
" ",
" you ",
" ",
" today ",
" ? ",
]
We'd like to remove the extra space, as well as remove any blank/empty tokens.
We can do this with the string's strip
method. However, we'd need to call strip
twice. Once in the list comprehension to check if it's an empty string, and then again for each expression we want to include in the final list.
An example:
cleaned_tokens = [token.strip() for token in tokens if token.strip()]
cleaned_tokens
['hello', 'world', '!', 'how', 'are', 'you', 'today', '?']
We do this with a single call to strip
using the walrus operator.
Note: the the expression with the walrus operator needs to be enclosed in parentheses.
cleaned_tokens = [cleaned_token for token in tokens if (cleaned_token := token.strip())]
cleaned_tokens
['hello', 'world', '!', 'how', 'are', 'you', 'today', '?']
What if we wanted to filer values out of the list comprehension?
Below, we'll filter our tokens that have less than 2 characters after being cleaned.
token_lengths = [len(token.strip()) for token in tokens if len(token.strip()) > 1]
token_lengths
[5, 5, 3, 3, 3, 5]
Again, we can do this with the walrus operator.
Note: the comparison operation needs to be outside the parentheses that enclose the walrus operator.
token_lengths = [len_token for token in tokens if (len_token := len(token.strip())) > 1]
token_lengths
[5, 5, 3, 3, 3, 5]
Outside of list comprehensions, the most common other use case seems to be in if
statements, where we want to "capture" a value in a variable.
For an example without using the walrus operator:
kv = {"a": 0, "b": 1, "c": 2}
def fn(character: str, d: dict):
index = d.get(character)
if index is not None:
return f"Found {character} at index {index}"
else:
return f"Couldn't find {character}"
fn("a", kv)
'Found a at index 0'
fn("z", kv)
"Couldn't find z"
fn
looks alright, but what if we could perform the get
and is None
and assign the value to index
all in one go?
def fn(character: str, d: dict):
if (index := d.get(character)) is not None:
return f"Found {character} at index {index}"
else:
return f"Couldn't find {character}"
fn("a", kv)
'Found a at index 0'
fn("z", kv)
"Couldn't find z"
There's also a way to use the walrus operator in while
loops (which I don't seem to use a lot).
letters = ["a", "b", "c", "d", "e"]
i = 0
while letters[i].upper() != "D":
print(f"Got: {letters[i].upper()}")
i += 1
Got: A Got: B Got: C
The above calls letters[i].upper()
twice, which we can simplify with our trusty walrus operator:
i = 0
while (upper_letter := letters[i].upper()) != "D":
print(f"Got: {upper_letter}")
i += 1
Got: A Got: B Got: C
Finally, using the walrus operator with the any
operator, where we want to find if any item in a list comprehension is True
, and an instance of one of those results.
Without the walrus operator, we have to do this ugly filter, check, index at zero:
animals = ["ant", "baboon", "cat", "dog", "elephant"]
c_animals = [animal for animal in animals if animal.startswith(("c", "C"))]
if c_animals:
print(f"Found {c_animals[0]}")
else:
print("No animal starts with C")
Found cat
However, with the walrus operator we can do all of that in a single line!
if any((c_animal := animal).startswith(("c", "C")) for animal in animals):
print(f"Found {c_animal}")
else:
print("No animal starts with C")
Found cat
There you go. There's some use cases for the walrus operator without confusing you by using the phrase "named expression" once!