Every time I’ve considered using the walrus operator, it felt like I’d be sucking time out of the rest of my team to scratch their heads at what the hell the code was doing.
I usually find that breaking out to an extra line the variable assignment is more readable. As for eliminating duplicate calls in comprehensions, I prefer the map idiom:
# Naive implementation: we call the expensive func twice here
result = [func(x) for x in data if func(x)]
# Suggested use of walrus
result = [y for x in data if (y := func(x))]
# I find map here is more readable
result = [y for y in map(func, x) if y]
I don’t understand what’s confusing about it. Either someone is new to Python, in which case you explain it the same way you’d explain any other syntax, or they’re not new to Python but new to the operator, in which case you explain the pattern it replaces. Or they look it up on their own. It’s not covering up some sort of super-deep-and-complex behavior, after all, so I don’t know what’s so horribly confusing to explain about it, compared to other bits of syntax Python has picked up in the 16-ish years that I’ve been writing it.
Nothing is confusing about it. I am all for avoiding clever code and being cognizant of your co-workers, but if the 2nd example is confusing to a professional Python programmer, the answer is for them to level up their skills, not for you to work around their incompetence.
You might say, “I can just add y = func(x) before the list declaration and I don’t need the walrus!”, you can, but that’s one extra, unnecessary line of code and at first glance - without knowing that func(x) is super slow - it might not be clear why the extra y variable needs to exist.
The walrus version makes it clear the y only belongs to that statement, whereas the “one extra line” version pollutes the local scope, and makes your intention less clear. For short functions this won’t matter much, but the argument is sound.
Local scope gets polluted all the damn time in Python. I’m not saying that’s desirable, but it is part of the language, and that’s a worse argument then most.
y with the walrus, as written in the article, pollutes local scope anyway, by the by, as the result of func(x). No intentionality is lost and you’ve done literally the same damn thing.
Do what you can’t do and you’ve got my attention. (For my earlier post, using it within an any, at least, takes advantage of short-circuiting (though implicit!))
Yeah, I find the walrus operator redundant in almost every case I’ve seen it used. If I’m feeling generous, I’ll give partial credit for the loop-and-a-half and the short-circuiting any behavior— but it was wrong to add to the language and I’ve banned it from any Python I’m in charge of.
Edit: Also, the accumulator pattern as written is horrendous. Use fold (or, yes, itertools, fine) as McCarthy intended
You can make the same argument with only minor modification of the examples against many things that now are considered not just acceptable but idiomatic Python.
For example, you always could just add the extra line to manually close a file handle instead of doing with open(some_file) as file_handle – so should we ban the with statement?
You could always implement coroutines with a bit more manual work via generators and the yield statement – should we ban async and await”?
You could always add the extra lines to construct an exception object with more context – should we ban raise from?
Hopefully you see the pattern here. Why is this bit of syntax suddenly such a clear hard line for some people when all the previous bits of syntax weren’t?
I once had a coworker that felt that functions were an unnecessary abstraction created by architecture astronauts. Everything people did with functions could be accomplished with GOTO. In languages without a GOTO statement, a “professional” programmer would write a giant while loop containing a series of if statements. Those if statements then compared with a line_number variable to see if the current line of code should be executed. The line_number was incremented at the end of the loop. You could then implement GOTO simply by assigning a new value to line_number. He argued that the resulting code was much more readable than having everything broken up into functions, since you could always see what the code was doing.
with is a dedicated statement. It does 2 things ok, but only them, no more. You can’t add complexity by writing it in another complex structure like a list-comprehension. The walrus can be put everywhere, it’s vastly different IMHO.
I also find that in result = [y, y**2, y**3] it’s much clearer to parse what’s going on at a quick glance, while you need to think more when coming up on the walrus.
Even clearer might be result = [y**1, y**2, y**3].
c = 0; print([(c := c + x) for x in data]) # c = 14
I would never allow this into my codebase. It has two nonstandard things: two statements on the same line, and side-effects inside a list comprehension.
When I see a list comprehension, I expect it to act like this:
[a, b, c, ...] -> (list comprehension) -> [f(a), f(b), f(c), ...] # possibly with some entries omitted because of the predicate clause
It may be valid code, but morally it’s not what comprehensions are for. It’s an abuse of notation. Just use an explicit loop for that, or itertools; don’t try to be clever for the sake of saving a few lines.
This is precisely the kind of mischief that made the walrus such a controversial addition.
“Morally” has a secondary meaning, something like “as a matter of practicality, experience, common sense”, often used to contrast a formal, theoretical, or pedantic judgement.
According to the evidence of human reason or of probabilities, founded on facts or experience; according to the usual course of things and human judgment.
Second thought was my doubts about the whole “less code is better code” mantra that appears in several places of the article. 7 to 4 lines of code does not ring like a very strong argument to me.
That being said, it was a very interesting read. Nice to see the clever ways this operator can shine.
I use it with regex like the example they provide in the article (if-s and while-s in general), where it is great in my opinion. The other usecases do indeed seem a bit contrived
this ☝💯, with one addition: the variable being assigned to should only be used inside that if/while block. (anything used afterwards deserves a normal assignment.)
Not to take a side, I won’t say the examples aren’t valid.
Personally I have never bothered to get used to it. I checked a few years ago what it did and didn’t feel like I needed it for anything. But that was just me.
This kind of article does feel a bit symptomatic. If you need to recommend it, it’s because it doesn’t offer an obvious immediate advantage that makes people adopt it. Python is possibly the language with the strongest culture of direct gains to the programmer. Often called pragmatism. The whole language is design with practicality and convenience in the center. The popularity of libraries like requests, flask, keras, scippy, etc. speak volumes about this.
A few years have passed and usage of walrus operator is not widespread. I think this hints that it isn’t a big deal in the end. To provide a comparison from other language:
When java introduced support for iterables in its for construct, everyone started using it in no time without making a fuss about it. The same happened for generics and later functional style utilities in java 8. These things clearly lower the effort and improved developer experience when achieving a given task. People naturally adopted them. And this was in a language with a much more conservative community.
I suspect the walrus operator doesn’t provide such a clear gain.
I don’t find the examples more readable with the operator. I often advise my team in code reviews to actually make use of as many lines as you need to clearly (yet concisely) tell future team members what the intention is behind your code. And most of the time, this involves reducing the amount of complexity that is being invoked on a single line of code. Maybe it’s just a Python thing, I haven’t been a Python dev for years.
another gotcha that bit me lately, walrus operations inside asserts:
assert isinstance(inner_field := message.data.inner_field)
assert (value := inner_field.something.else) is not None
some_function(value)
Sometimes we need to assert that some field is of a specified type or is not None to satisfy MyPy. we started adopting walrus operators in the asserts to make the code a little more compact and readable. Our test suites ensure that these invariants should hold, so we wanted to turn on PYTHON_OPTIMIZE=1 which strips assert checking at runtime. Unfortunately it also strips the whole assert statement including the walrus assignment which obviously breaks our code.
It does make sense to use it in some cases, but using it for cheeky one-liners and further code golf seems a bit too much and adds a lot of visual noise to things like list comprehensions. Most of it’s original use-cases in it’s PEP made it sound a lot like an anaphoric if statement from Lisp to me, but beyond that I think adding more noise to the code snippets is a bit too much.
I don’t hate the Walrus operator, but I also don’t find this to make enough of a compelling case for why I should introduce the increased cognitive complexity it brings to the table into my code, or why I shouldn’t advise against its use when I see it in code reviews.
One of the things I really value about Python is its un-cleverness. I see the Walrus operator as a decidedly clever syntax.
Take the first example in the article:
# "func" called 3 times
result = [func(x), func(x)**2, func(x)**3]
# Reuse result of "func" without splitting the code into multiple lines
result = [y := func(x), y**2, y**3]
I can look at the first code snippet and understand exactly what it’s doing without guessing.
I realize this all comes down to preference and stylistic choices, but I’m not a fan of the walrus operator and this article hasn’t changed that.
I’m sorry. You used the words ‘efficiency’ and ‘Python’ in the same article.
Python is the first language in some time to emphasize clarity, and programmer efficiency, solidly over execution time. While one can construct examples where the walrus operator provide more clarity than alternatives, these examples are all concerned with execution speed.
It’s not that the point is wrong. It’s that the article does not address if it is right or wrong.
Is a variable assignment being also an expression a useful concept? Sure. Is it a good idea to incorporate that concept in python? I don’t think so.
Some other languages have it, as you could do in C if ((fp = fopen(...)) != NULL) {, because that’s an idiom and consistent with the rest of the language. There’s no problem with that.
In python though, everything was designed against this kind of pattern. Stuff should be made explicit, the Zen of python recommends it, the whole ecosystem tries to follow it, and the language does its share to help being like that. i++ doesn’t exist. The walrus operator is an anomaly to the rest of the language and a slap in the face to everything that was built before.
Every time I’ve considered using the walrus operator, it felt like I’d be sucking time out of the rest of my team to scratch their heads at what the hell the code was doing.
I usually find that breaking out to an extra line the variable assignment is more readable. As for eliminating duplicate calls in comprehensions, I prefer the
map
idiom:I don’t understand what’s confusing about it. Either someone is new to Python, in which case you explain it the same way you’d explain any other syntax, or they’re not new to Python but new to the operator, in which case you explain the pattern it replaces. Or they look it up on their own. It’s not covering up some sort of super-deep-and-complex behavior, after all, so I don’t know what’s so horribly confusing to explain about it, compared to other bits of syntax Python has picked up in the 16-ish years that I’ve been writing it.
Nothing is confusing about it. I am all for avoiding clever code and being cognizant of your co-workers, but if the 2nd example is confusing to a professional Python programmer, the answer is for them to level up their skills, not for you to work around their incompetence.
I found the map example a bit confusing because
x
is a scalar value in the other examples.Shouldn’t it be this way?
It is all personal preference, but for almost all of those cases, I found the versions with more lines to be easier to read/understand.
I don’t get it. Is this just to avoid writing one extra line, like this?
The article explicitly addresses this objection:
The walrus version makes it clear the
y
only belongs to that statement, whereas the “one extra line” version pollutes the local scope, and makes your intention less clear. For short functions this won’t matter much, but the argument is sound.with y = func(x):
Local scope gets polluted all the damn time in Python. I’m not saying that’s desirable, but it is part of the language, and that’s a worse argument then most.
y
with the walrus, as written in the article, pollutes local scope anyway, by the by, as the result offunc(x)
. No intentionality is lost and you’ve done literally the same damn thing.Do what you can’t do and you’ve got my attention. (For my earlier post, using it within an
any
, at least, takes advantage of short-circuiting (though implicit!))You are correct, I should have tested myself before saying that. So yeah, the benefits look much weaker than I had supposed.
Yeah, I find the walrus operator redundant in almost every case I’ve seen it used. If I’m feeling generous, I’ll give partial credit for the loop-and-a-half and the short-circuiting any behavior— but it was wrong to add to the language and I’ve banned it from any Python I’m in charge of.
Edit: Also, the accumulator pattern as written is horrendous. Use
fold
(or, yes, itertools, fine) as McCarthy intendedYou can make the same argument with only minor modification of the examples against many things that now are considered not just acceptable but idiomatic Python.
For example, you always could just add the extra line to manually close a file handle instead of doing
with open(some_file) as file_handle
– so should we ban thewith
statement?You could always implement coroutines with a bit more manual work via generators and the
yield
statement – should we banasync
andawait
”?You could always add the extra lines to construct an exception object with more context – should we ban
raise from
?Hopefully you see the pattern here. Why is this bit of syntax suddenly such a clear hard line for some people when all the previous bits of syntax weren’t?
I once had a coworker that felt that functions were an unnecessary abstraction created by architecture astronauts. Everything people did with functions could be accomplished with GOTO. In languages without a GOTO statement, a “professional” programmer would write a giant
while
loop containing a series ofif
statements. Thoseif
statements then compared with aline_number
variable to see if the current line of code should be executed. Theline_number
was incremented at the end of the loop. You could then implement GOTO simply by assigning a new value toline_number
. He argued that the resulting code was much more readable than having everything broken up into functions, since you could always see what the code was doing.“If the language won’t let us set the CPU’s IP register, we’ll just make our own in memory!”
Yikes.
with
is a dedicated statement. It does 2 things ok, but only them, no more. You can’t add complexity by writing it in another complex structure like a list-comprehension. The walrus can be put everywhere, it’s vastly different IMHO.I also find that in
result = [y, y**2, y**3]
it’s much clearer to parse what’s going on at a quick glance, while you need to think more when coming up on the walrus.Even clearer might be
result = [y**1, y**2, y**3]
.I would never allow this into my codebase. It has two nonstandard things: two statements on the same line, and side-effects inside a list comprehension.
When I see a list comprehension, I expect it to act like this:
That accumulating example instead does this:
It may be valid code, but morally it’s not what comprehensions are for. It’s an abuse of notation. Just use an explicit loop for that, or itertools; don’t try to be clever for the sake of saving a few lines.
This is precisely the kind of mischief that made the walrus such a controversial addition.
There are morals in which features of a programming language you use now?
“Morally” has a secondary meaning, something like “as a matter of practicality, experience, common sense”, often used to contrast a formal, theoretical, or pedantic judgement.
https://webstersdictionary1828.com/Dictionary/morally
See also https://english.stackexchange.com/questions/116722/morally-speaking-11-2
I’ve never seen that use of it, appreciate the sources
Python pioneered having a Zen, an ordered list of design principles. So, yes, one could argue it is morally wrong.
Ah yes.. A list of platitudes. One look at
str
methods shows that was thrown out the window.I’ve only been using them on
if
‘s andwhiles
. Other uses get fairly confusing to read. But at those ones it’s great.My thought exactly, while reading the article.
Second thought was my doubts about the whole “less code is better code” mantra that appears in several places of the article. 7 to 4 lines of code does not ring like a very strong argument to me.
That being said, it was a very interesting read. Nice to see the clever ways this operator can shine.
I use it with regex like the example they provide in the article (if-s and while-s in general), where it is great in my opinion. The other usecases do indeed seem a bit contrived
this ☝💯, with one addition: the variable being assigned to should only be used inside that
if
/while
block. (anything used afterwards deserves a normal assignment.)It’s unfortunate that this isn’t enforced because of Python’s weird scoping rules.
Not to take a side, I won’t say the examples aren’t valid. Personally I have never bothered to get used to it. I checked a few years ago what it did and didn’t feel like I needed it for anything. But that was just me.
This kind of article does feel a bit symptomatic. If you need to recommend it, it’s because it doesn’t offer an obvious immediate advantage that makes people adopt it. Python is possibly the language with the strongest culture of direct gains to the programmer. Often called pragmatism. The whole language is design with practicality and convenience in the center. The popularity of libraries like requests, flask, keras, scippy, etc. speak volumes about this.
A few years have passed and usage of walrus operator is not widespread. I think this hints that it isn’t a big deal in the end. To provide a comparison from other language: When java introduced support for iterables in its for construct, everyone started using it in no time without making a fuss about it. The same happened for generics and later functional style utilities in java 8. These things clearly lower the effort and improved developer experience when achieving a given task. People naturally adopted them. And this was in a language with a much more conservative community.
I suspect the walrus operator doesn’t provide such a clear gain.
Are lines of code in short supply?
I don’t find the examples more readable with the operator. I often advise my team in code reviews to actually make use of as many lines as you need to clearly (yet concisely) tell future team members what the intention is behind your code. And most of the time, this involves reducing the amount of complexity that is being invoked on a single line of code. Maybe it’s just a Python thing, I haven’t been a Python dev for years.
I tried to show restraint in my response but WOW this exactly articulates my issue with the whole thing.
another gotcha that bit me lately, walrus operations inside asserts:
Sometimes we need to assert that some field is of a specified type or is not
None
to satisfy MyPy. we started adopting walrus operators in the asserts to make the code a little more compact and readable. Our test suites ensure that these invariants should hold, so we wanted to turn onPYTHON_OPTIMIZE=1
which strips assert checking at runtime. Unfortunately it also strips the whole assert statement including the walrus assignment which obviously breaks our code.Jesus. Why did you think it was a good idea to do anything other than the assert tests within asserts.
It does make sense to use it in some cases, but using it for cheeky one-liners and further code golf seems a bit too much and adds a lot of visual noise to things like list comprehensions. Most of it’s original use-cases in it’s PEP made it sound a lot like an anaphoric if statement from Lisp to me, but beyond that I think adding more noise to the code snippets is a bit too much.
There are some places where it’s nice to use it. Some of the list comprehensions are a great example. Inline regex checking, a great example.
But the things like the any/all tricks are… well, they’re tricks. It certainly doesn’t feel pythonic.
I thought the any/all trick was one of the neater uses for it; the non walrus alternative looks a lot clunkier.
That syntax should have been
let <name> = <expr> in ...
. Or, if they really like colons,let <name> := <expr> in ...
. :)I don’t hate the Walrus operator, but I also don’t find this to make enough of a compelling case for why I should introduce the increased cognitive complexity it brings to the table into my code, or why I shouldn’t advise against its use when I see it in code reviews.
One of the things I really value about Python is its un-cleverness. I see the Walrus operator as a decidedly clever syntax.
Take the first example in the article:
I can look at the first code snippet and understand exactly what it’s doing without guessing.
I realize this all comes down to preference and stylistic choices, but I’m not a fan of the walrus operator and this article hasn’t changed that.
So clearly you also understand that it’s doing a different thing than the second snippet?
No, you shouldn’t.
I’m sorry. You used the words ‘efficiency’ and ‘Python’ in the same article.
Python is the first language in some time to emphasize clarity, and programmer efficiency, solidly over execution time. While one can construct examples where the walrus operator provide more clarity than alternatives, these examples are all concerned with execution speed.
It’s not that the point is wrong. It’s that the article does not address if it is right or wrong.
Is a variable assignment being also an expression a useful concept? Sure. Is it a good idea to incorporate that concept in python? I don’t think so.
Some other languages have it, as you could do in C
if ((fp = fopen(...)) != NULL) {
, because that’s an idiom and consistent with the rest of the language. There’s no problem with that.In python though, everything was designed against this kind of pattern. Stuff should be made explicit, the Zen of python recommends it, the whole ecosystem tries to follow it, and the language does its share to help being like that.
i++
doesn’t exist. The walrus operator is an anomaly to the rest of the language and a slap in the face to everything that was built before.Hmmm…. One might guess you like loud politics. Shouting where reason fails.
assert (form := page.query_selector(“form#foo”))
It’s great, immediately started using it more.