Hacker News new | past | comments | ask | show | jobs | submit login

Walrus operator IMO is a huge mistake. It saves fuck all time/space, but introduces an entire new operator that probably added more complexity, edge cases and quirks than the benefits it provides. Terrible idea.

Worse, no one uses it. I've yet to come across anyone that advocates it or remembers it.




I actually just used it to what I would say was a perfect example of where it legitimately improved code readability while maintaining pythonic constructs:

    values = [
        value
        for line in buffer.readlines()
        if (value := line.strip())
    ]
Previously, I would have needed to either duplicate effort like:

    values = [
        line.strip()
        for line in buffer.readlines()
        if line.strip()
    ]
Or used a sub-generator:

    values = [
        value
        for value in (
            line.strip() for line buffer.readlines()
        )
        if value
    ]
Or rewritten it altogether using a (slower) for loop calling append each time:

    values = []
    for line in buffer.readlines():
        line = line.strip()
        if line:
            values.append(line)
The assignment expression is perfect for this sort of use case, and is a clear win over the alternatives IMO.

Edit: fixed initial example


I may have gone with the following. Yes, some characters are repeated, but I'm not playing code golf.

  stripped_lines = (line.strip() for line in buffer.readlines())
  non_empty_stripped_lines = [line for line in stripped_lines if stripped_lines]


Way better IMO. Clear variable names. No golfing.

Breaking down things in clear steps is underrated I think.


You can write it this way:

    values = [
        value
        for line in buffer.readlines()
        for value in (line.strip(),)
        if value
    ]


FWIW, that version ends up being slower, because you're constructing and iterating over a tuple for every iteration, which incurs a similar cost to running `.strip()` twice. The sub-generator example I gave is better because you're only constructing the generator expression once, and requires less overhead for each iteration.


It is unlikely (unless there is a benchmark that says otherwise). Before the walrus operator, the single item loop could have been used:

  nonblank = [value for line in file for value in [line.strip()] if value]


To me the second version seems not only clearer/more direct to follow but also is a few characters shorter anyways.


But the second version needs to run `.strip` twice. It might not make much of a difference for `strip` -- but it still hurts my eyes, and could be an actual performance issue for other operations.


Running strip twice makes it more explicit and readable IMHO - it's then abundantly clear that it's being run as a check and as a way of populating the list.


values = list(filter(None, [line.strip() for line in buffer.readlines()]))


Or, in a different language

  open("file", "r").readlines.
    map{|line| line.strip}.
    filter{|line| line != ""}
or some smarter but less readable ways.

I prefer the left-to-right transformations style to Python's list comprehension and inside-to-outside function composition. The reason is that it reminds me of how data flow into *nix pipelines. I spent decades working with them and I've been working with Ruby for the last half of that time. With Python in the last quarter of my career.

It's a matter of choices and preferences of the original designed of the language. Both ways work.


What does `filter` do with `None`? Would it not be an error? This seems not so readable, possibly relying on weird behavior of `filter`. If I had to guess, I would say: Maybe filter with `None` will give the empty list or the whole list, because either no list item matches a `None` condition, or all match it, since there is no condition. But in both cases the usage does not seem to make any sense. So it must be something else. Maybe when the argument is not a lambda, it will compare directly with the given argument? But then we would get only `None`. Nah, still makes no sense. I am at a loss, what this `filter` call is doing, without trying it out.


> What does `filter` do with `None`?

  filter(None, xs)
is equivalent to:

  filter(lambda x: x, xs)
That is, it will return an iterator over the truthy elements of the passed iterable.


I suspect the question was rhetorical. The point is, every reader is going to have that question pop into their head and have to look it up. Better to use code that doesn't raise any questions, even if it's a few more characters.


> Better to use code that doesn't raise any questions, even if it's a few more characters.

Certainly, I agree; I would usually use:

  (x for x in xs if x)
Or, if I know more about the kind of falsy values xs actually needs removed, something more explicit like:

  (x for x in xs if x is not None)
Because Python’s multiplicity of falsy values can also be something of a footgun (particularly, when dealing with something a collection of Optionals where the substantive type has a falsy value like 0 or [] included.)

Instead of:

  filter(None, xs)
Which is terse but potentially opaque.

Though it's additional syntax, I kind of wish genexp/list/set comprehensions could use something like “x from” as shorthand for “x for x in”, which would be particularly nice for filtering comprehensions.


From the docs:

If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.

So it just removes false-y values.

Very handy I've used it a ton


Not sure if this was your intention or not, but to me that proves the usefulness of the walrus operator: the first snippet in the parent comment seems far clearer to me, even though I'm fairly familiar with the functional operators.


It's a very useful pattern once you know what it does, sort of like the walrus operator


I was careful to pre-empt this exact response in my original comment: I do know what it does. The fact remains that it's less readable (IMO) because of the density of line noise and the lack of common structural elements (like if and for - I suppose filter and map fulfill this but their parameter separate out elements that ought to be next to each other). I do think that my preference, however slight, would remain no matter how much time I spent with the functional versions.


I'm glad you know what it does but you weren't born knowing what the walrus operator does either


I use it for simple cases:

    >>> if m := re.search(r'(.*)s', 'oh!'):
    ...     print(m[1])
    ... 
    >>> if m := re.search(r'(.*)s', 'awesome'):
    ...     print(m[1])
    ... 
    awe


The question one has to ask is whether it is worth the additional complexity and a dedicated operator. I am sure it is ever so slightly useful, but I am not convinced it is worth the trouble.

Feature creep is programming language's worse enemy after a certain maturity level.

I absolutely love Go in this matter. They took forever to add Generics and generally sides with stability over features.


I'm surprised no one's mentioned a while loop yet:

    start = 0
    while (end := my_str.find("x", start)) != -1:
        print(my_str[start:end])
        start = end + 1
vs

    start = 0
    while True:
        end = my_str.find("x", start)
        if end == -1:
            break
        print(my_str[start:end])
        start = end + 1
I'm still on the fence myself so I sympathise with your view, but the first version is certainly a bit tidier in this case.


I never use the walrus operator but your second example is pretty typical and it does indeed look a lot cleaner with the walrus operator.


Sure it's "tidier" if by that you mean smaller. Someone who doesn't work in Python all the time and isn't aware of these kinds of operators is going to have to spend a decent amount of time unpacking what the hell that all means whereas someone can take one look at the standard while loop, see the logic laid out plainly, understand what's happening, and make changes, if necessary, fairly easily. Unless there's a performance benefit to an operator like this I'll forgo "tidy" for clear any day of the week. Then again I'm just a senior dev whose only professional experience with Python was maintaining other people's Python projects who never had to touch them again after they wrote them, and who used Python for things Python should not have been used for just because it's "easy" to write.


Funny, I think the walrus operator makes code cleaner and easier to understand.

Many of my code were like this:

    foo = one_or_none()
    if foo:
        do_stuff(foo)
Now I have the following:

    if foo := one_or_none():
        do_stuff(foo)
This kind of code happens quite frequently, looks nicer with walrus operator to me.


I haven't yet fully adjusted to the walrus operator, but to me the choice would depend on what happens _after_ the "if" statement.

In both cases, "foo" continues to exist after the "if" even though the second example makes it look like "foo" is scoped to the "if".

So to my eye, the following would look super weird (assume do_more_stuff can take None):

    if foo := one_or_none():
        do_stuff(foo)
    do_more_stuff(foo)
whereas the following would look fine:

    foo = one_or_none()
    if foo:
        do_stuff(foo)
    do_more_stuff(foo)


Honestly, for this specific case, I prefer one_or_none() to return an iterable with zero or one items, and then just doing:

  for foo in one_or_none():
    do_stuff(foo)
If you don't control one_or_none, but it returns an Optional, you can wrap it with something like:

  def optional_to_tuple(opt_val: Optional[T]) -> Tuple[]|Tuple[T]:
    return (opt_val,) if opt_val is not None else ()


Would have been more Pythonic as:

    if one_or_none() as foo:
        do_stuff(foo)


I didn't recognize this use of "as" as valid Python, but tried it in 3.9 just to be sure. Got a syntax error (as expected).

I am not fully up to speed with 3.10, but quickly checked the docs and it doesn't appear to have been added in 3.10 either.

Let me know if I'm missing something.


Oh, "would have" meaning python-dev chose a different spelling. Not, as in it would-have been better if the example was written this way.


Oh, I see now. Thanks for the clarification.


Walrus avoids stuttering in constructs like comprehensions/genexps which greatly improves readability. (And avoids using less basically readable constructs, like explicit imperative loops, to avoid the visual noise of stuttering.)


The let expression syntax in OCaml is a nice alternative:

  let some_func () = 5
  
  let a = some_func () in if a < 5 then "<5" else ">=5"




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: