If you haven’t heard, Python 3.8 features a rather controversial new operator called the walrus operator. In this article, I’ll share some of my first impressions as well as the views from all sides. Feel free to share some of your thoughts as well in the comments.
Recently, I was browsing dev.to, and I found a really cool article by Jason McDonald which covered a new feature in Python 3.8, the walrus operator. If you haven’t seen the operator, it looks like this:
In this article, Jason states that the new operator “allows you to store and test a value in the same line.” In other words, we can compress this:
nums = [87, 71, 58] max_range = max(nums) - min(nums) if max_range > 30: # do something
nums = [87, 71, 58] if (max_range := max(nums) - min(nums)) > 30: # do something
In this example, we saved a line because we moved the assignment into the condition using the walrus operator. Specifically, the walrus operator performs assignment while also returning the stored value.
In this case,
max_range will store 29, so we can use it later. For example, we might have a few additional conditions which leverage
nums = [87, 71, 58] if (max_range := max(nums) - min(nums)) > 30: # do something elif max_range < 20: # do something else
Of course, if you’re like me, you don’t really see the advantage. That’s why I decided to do some research.
When I first saw this syntax, I immediately thought “wow, this doesn’t seem like a syntax that meshes well with the Zen of Python.” In fact, after revisiting the Zen of Python, I think there are several bullet points this new syntax misses.
While beauty is in the eye of the beholder, you have to admit that an assignment statement in the middle of an expression is kind of ugly. In the example above, I had to add an extra set of parentheses to make the left expression more explicit. Unfortunately, extra parentheses reduce the beauty quite a bit.
If the intent of the walrus operator is to compress two lines into one, then that directly contradicts “sparse is better than dense.” In the example I shared above, the first condition is fairly dense; there’s a lot to unpack. Wouldn’t it always make more sense to place the assignment on a separate line?
If you’re looking for a good example of a feature that compresses code, take a look at the list comprehension. Not only does it reduce nesting, but it also makes the process of generating a list much simpler. To be honest, I don’t get that vibe with the walrus operator. Assignment is already a pretty easy thing to do.
In the example above, I introduced parentheses to make the condition more explicit. Had I left out the parentheses, it becomes a little more difficult to parse:
if range := max(nums) - min(nums) > 30:
In this case, we have several operators on a single line, so it’s unclear which operators take precedence. As it turns out, the arithmetic comes first. After that, the resulting integer is compared to 30. Finally, the result of that comparison (
False) is stored in range and returned. Would you have guessed that when looking at this line?
To make matters worse, the walrus operator makes assignment ambiguous. In short, the walrus operator looks like a statement, but it behaves like an expression with side effects. If you’re unsure why that might be an issue, check out my article on the difference between statements and expressions.
One of the interesting things about this operator is that it now introduces a completely new way to perform assignment. In other words, it directly violates the “there should be only way way to do it” rule.
That said, I’m a little bit on the fence with this one because the new operator is more explicit. In other words, it differentiates the intent behind
=. In addition, it reduces potential bugs related to confusing
== in conditions.
Likewise, as far as I can tell, you can’t just use the walrus operator in all the same places you would use assignment. In fact, they’re completely different operators. Unfortunately, nothing is really stopping you from doing something like this:
(x := 5)
I don’t know why you would ever do this, but it’s now very legal code. Luckily, PEP 572 prohibits it. Of course, that doesn’t stop code like this from appearing in the wild. In fact, the documentation lists a handful of ways the new syntax can be abused. That’s not a good sign!
At this point, I sort of drew the line with this new feature. As I dug around to read others’ opinions on the subject, I found the following gold nugget:
This feels very Perl-y in the example given, in that it requires that you know what yet another operator means to read code that uses it. Since Python is supposed to be “executable pseudocode” (roughly), this kind of new operator might increase the amount of learning that a beginner has to do to read others’ code. I hope that this decision does not pave the way for more like it, because it would make Python code much less readable to someone who hasn’t studied the new operators yet.
That’s when I realized why I love Python so much. It’s just so damn easy to read. At this point, I really feel like the addition of this operator was a mistake.
As with anything, I hate to form an opinion without really digging into the topic, so I decided to hear from the folks who were excited about this feature. To my surprise, I found a lot of cool examples.
By far, the strongest case for the new walrus operator is in while loops. Specifically, I liked the example by Dustin Ingram which leveraged the operator to remove duplicate lines of code. For example, we can convert this (source):
chunk = file.read(8192) while chunk: process(chunk) chunk = file.read(8192)
while chunk := file.read(8192): process(chunk)
By introducing the walrus operator, we remove a duplicate line of code. Now, every time the loop iterates, we automatically update
chunk without having to initialize it or update it explicitly.
Seeing this example is enough for me to see the value in the walrus operator. In fact, I’m so impressed by this example that it made me wonder where else this could be used to improve existing code.
That said, I did dig around, and some folks still felt like this was a bad example. After all, shouldn’t file reading support an iterable? That way, we could use a for loop, and this wouldn’t be an issue at all. In other words, isn’t the walrus operator just covering up for bad library design? Perhaps.
As an avid list comprehension enthusiast, I’ve found that the walrus operator can actually improve efficiency by allowing us to reuse calculations. For example, we might have a comprehension which looks like this:
[determinant(m) for m in matrices if determinant(m) > 0]
In this example, we build up a list of determinants from a list of matrices. Of course, we only want to include matrices whose determinants are greater than zero.
Unfortunately, the determinant calculation might be expensive. In addition, if we have a lot of matrices, calculating the determinant twice per matrix could be costly. Luckily, the walrus operator is here to help:
[d for m in matrices if (d := determinant(m)) > 0]
Now, we only calculate the determinant once for each matrix. How slick is that?
Beyond the two examples above, I’ve seen a few other examples including pattern matching, but I don’t really have an appreciation for it. Honestly, the other examples just seem kind of niche.
For instance, PEP 572 states that the walrus operator helps with saving expensive computations. Of course, the example they provide is with constructing a list:
[y := f(x), y**2, y**3]
Here, we have a list that looks like this:
[y, y**2, y**3]
In other words, what’s stopping us from declaring y on a separate line?
y = f(x) [y, y**2, y**3]
In the list comprehension example above, I get it, but here I don’t. Perhaps there’s a more detailed example which explains why we’d need to embed an assignment statement in list creation. If you have one, feel free to share it in the comments.
Now that I’ve had a chance to look at the new walrus operator more or less objectively, I have to say that I think my first impressions still stand, but I’m willing to be persuaded otherwise.
After seeing a few solid examples, I was still really skeptical, so I decided to take a look at the rationale behind the operator in PEP 572. If you get a chance, take a look at that document because it’s enormous. Clearly, this decision was well thought out. My only fear is that the authors were persuaded to include the feature by shear sunk cost fallacy, but who knows.
If you read through PEP 572, you’ll see 79 code blocks across the entire page. To me, that’s just a ridiculous amount of examples. To make matters worse, a large portion of the examples show edge cases where the operator won’t work or wouldn’t be ideal rather than where it would provide an edge. For instance, take a look at some of these examples:
x = y = z = 0 # Equivalent: (z := (y := (x := 0))) x = 1, 2 # Sets x to (1, 2)(x := 1, 2) # Sets x to 1 total += tax # Equivalent: (total := total + tax)
That said, the authors did go as far as to provide some examples from their reworked standard library. Of course, these examples are much larger, so I won’t share them here. However, you’re welcome to take a peek.
Personally, I think the examples linked above illustrate the advantage of the walrus operator much better than some of the cases I shared in the counterpoint section. Specifically, any time the walrus operator removes duplicate or nested code, I’m happy with it. Otherwise, it seems to have very few obvious use cases.
My worry is that adding a new operator adds unnecessary complexity to the language, and I’m not convinced that the pros outweigh the cons. At any rate, I trust the decision of the team that put it together, and I’m excited to see how the community uses it!
With all that said, thanks again for showing your support and checking out my work. If you’re new here, I’d appreciate it if you hopped on my mailing list or even joined me on Patreon. If you do decide to lay down some cash, there’s a ton in it for you including having an article written about you, getting coupon codes to the store, and gaining access to premium articles.
Speaking of my store, you might find some value in my Python 3 Cheat Sheet which features two full pages of beginner-related language features like loops, conditionals, and comprehensions. If you head over there now, you can catch 10% off with the coupon code RENEGADE.
Alternatively, you’re welcome to stick around and check out some of my other Python articles:
- How to Format a String in Python
- Yet Another Way to Learn Recursion
- Rock Paper Scissors Using Modular Arithmetic
As always, thanks for stopping by! See you back soon.
The post The Controversy Behind The Walrus Operator in Python appeared first on The Renegade Coder.