Writing the code takes 90% of the project time. Debugging it takes the other 90%.
Hey pips!
30 issues of pyco:bytes. My time’s almost up :0
In this issue, we’ll look at an operator you’ve been seeing every pyco:bytes issue, but probably never before in Python.
pycobytes := "yooooo"
Hell yeah, it’s finally time to explain what that :=
means. Turns out it was a Python reference all along. Who would’ve known?
Its official name is “Assignment Expressions” or “Named Expressions”, according to PEP 572↗. But most people call it the walrus operator – supposedly because it looks similar to the face and tusks of a walrus.
Image by Nixette, CC BY-SA 4.0, via Wikimedia Commons
So what does it do? It’s quite simple.
:=
does the exact same thing as =
, except it returns the variable afterwards. Watch:
>>> test = "hi"
# nothing returned
>>> (test := "hi")
"hi"
So just like the official name suggests, they let you make an assignment as an expression rather than a statement.
# standalone statement
sup = 2.0
# expression in a statement
if (sup := sup + 1) == 3.0:
...
It’s worth noting that anything you can do with :=
, you could already do in Python. It doesn’t introduce any new functionality, it just helps make certain code more concise.
One of the best situations to see it in action is a while
loop where we need to use the output of the condition.
while database.fetch_value():
...
Here, we’re looping as long as the value returned by fetch_value()
is truthy. But what if we wanted to use that value?
while database.fetch_value():
value = database.fetch_value()
This probably wouldn’t work, because the second call to fetch_value()
is grabbing a different value. So we’d up having to do some wrangling…
while True:
data = database.fetch_value()
if not data:
break
...
Now the intent of the loop is less clear, and we’ve got a lot of boilerplate to essentially simulate the while cond:
loop.
So, let’s swap that out for a walrus! Now we can directly assign the output of fetch_value()
to a variable, and check it as a condition at the same time.
while (data := database.fetch_value()):
...
Collapsed onto 1 line, and it’s actually way cleaner.
This isn’t always the case, though. If we were using an if
,
item = user.items.pop()
if item in BANNED_ITEMS:
item.destroy()
We could similarly collapse it with :=
to give
if (item := user.items.pop()) in BANNED_ITEMS:
item.destroy()
We’ve decreased the line count by 1, but we’ve barely reduced the amount of code. More importantly, this single line is a lot busier than in the while
loop, so I wouldn’t say it’s worth sacrificing clarity here for the reduced line count.
We can also use :=
in iterable comprehensions, where everything is inline.
{
user.id: user.score
for each in search
if (user := database.find_user(each))
}
A pitfall is that you can only use the assignment inside an if
expression, not after the for
. This should make sense since what comes after for
is an identifier (variable) for the comprehension to iterate over – and a :=
expression isn’t an identifier.
# valid
[(expression) for (identifier) in (iterable)]
# invalid
[(expression) for (expression) in iterable]
Personally, while I find :=
super fun, in all honesty it rarely makes for cleaner code. Not bad by any means to have it as an option in your toolkit, but perhaps save it for the one-line challenges!
Further Reading
- In-depth use cases for assignment expressions – RealPython↗
Challenge
Can you write a one-line expression that outputs the quotient and remainder of 2 numbers the user inputs? (without using divmod()
lol)
>>> (your_expression) 10 3
3 1
# 10 / 3 = 3 remainder 1
>>> (your_expression) 49 7
7 0
>>> (your_expression) 4 5
0 4
Hint: walrus may be helpful here!