Walrus operator in Python 3.8: a primer
Assignment expressions (
:=), or the "walrus" operator, have been the most talked about feature to be introduced in the latest version of Python. The new addition to the language was proposed in PEP 572. In this post, we look at the rationale behind assignment expressions, and understand how to use it with various examples.
The primary rationale behind introducing assignment expressions, as stated in the PEP, is the observation that programmers want to save lines of code, and would even repeat a subexpression (and thereby slow down the program) in order to do so.
So instead of writing:
match = re.match(data) group = match.group(1) if match else None
group = re.match(data).group(1) if re.match(data) else None
Assignment in expressions make it easier to write code constructs where a value calculated is re-used as part of some condition later, and help make the intent of the code's author clearer. Languages like C and Go, among others, already support a functionality like this.
Where to use the Walrus operator
Let's have a look at few examples on how to use walrus operator in Python:
Example 1: When a calculated value is used as part of a condition later
match = pattern.search(data) if match is not None: do_something(match)
can be written as:
if (match := pattern.search(data)) is not None: do_something(match)
The above piece of code can be read as: if match, which becomes pattern.search of data, is not None, do_something of match.
Example 2: When reading a generator resource that exhausts
chunk = resource.read(8192) while chunk: process(chunk) chunk = resource.read(8192)
can be written as much shorter:
while chunk := resource.read(8192): process(chunk)
Another example when reading all lines in a file:
line = f.readline() while line: do_something(line) line = f.readline()
can be written succinctly as:
while line := f.readline(): do_something(line)
Example 3: Reusing a value that's expensive to compute
filtered_data = [ f(x) for x in data if f(x) is not None ]
can be written as:
filtered_data = [ y for x in data if (y := f(x)) is not None ]
This saves re-calculation of
f(x) and can improve performance of the program, while still keeping the line count to the same.
The authors of the PEP have also rationalized the feature by showing real-world examples from the Python standard library where using the walrus operator would save lines and make the intent clearer.
Where not to use the Walrus operator
Assignment expressions are not allowed in certain cases to avoid ambiguities — such as when used at the top level of an expression statement without parentheses. This simplifies the choice for the user between an assignment statement (
=) and an assignment expression (
:=) — there is no syntactic position where both are valid.
y := f(x) # INVALID (y := f(x)) # Valid, though not recommended y0 = y1 := f(x) # INVALID y0 = (y1 := f(x)) # Valid, though discouraged foo(x = y := f(x)) # INVALID foo(x=(y := f(x))) # Valid, though probably confusing
Things to keep in mind
An assignment expression does not introduce a new scope, and is bound by the current scope. If the current scope contains a
globaldeclaration for the target, the assignment expression honors that.
There is one special case: an assignment expression occurring in a
dictcomprehension or in a generator expression binds the target in the containing scope, honoring a
globaldeclaration for the target in that scope, if one exists. So it's possible to do something like this:
total = 0 partial_sums = [total := total + v for v in values] print("Total:", total)
However, an assignment expression target name cannot be the same as a
for-target name appearing in any comprehension containing the assignment expression.
What are your opinions on the Walrus operator? Do you think it is going to simplify writing code, or rather decrease the readability? Let us know @DeepSourceHQ!