6 ms·
> The "walrus operator" will occasionally be useful, but I doubt I will find many effective uses for it. The primary one I want is if m := re.match(...):
by geofft 7y ago
> The "walrus operator" will occasionally be useful, but I doubt I will find many effective uses for it.
The primary one I want is
if m := re.match(...):
print(m.group(1))
and
while s := network_service.read():
process(s)
both of which are both clearer and less error-prone than their non-walrus variants.
The other one that I would have found useful an hour ago is in interactive exploration with comprehensions. I frequently take a look at some data with [i for i in data if i[something].something...], and being able to quickly give a name to something in my conditional, as in {i: x for i in data if x := i[something]}, helps maintain the focus on data and not syntax. Obviously it will get rewritten to have clearer variable names, at the least, when it gets committed as real code, and almost certainly rewritten to be a normal non-comprehension for loop.
Like comprehensions, I expect the walrus operator to be valuable when used occasionally, and annoying if overused. There's no real language-level solution to bad taste. In Python 3 you can now do [print(i) for i in...], and I occasionally do at the REPL, but you shouldn't do it in real code and that's not an argument against the language supporting comprehensions.
- drdaeman 7y agoComing from Perl, I used to want this badly, but then I thought that there's absolutely nothing wrong with m = re.match(...) if m is not None: pass Now, I wonder what you meant saying that single-line version is less error-prone, because I don't think so. I believe they're exactly the same in this regard, except for a bizarre case when someone would bastardize the code by putting some irrelevant lines between the assignment and the comparison, obfuscating the logic. Also, as I tend to use `if m is not None:` rather than just `if m:` (because I typically don't want to run all that subtle `__nonzero__` magic), it would - subjectively - look less pretty in the one-line version: `if (m := re.match(...)) is not None:`. Use in the generator expressions to create aliases/shortcuts and avoid repetition is great, though. I love this use case.
- 3PS 7y agoThe issue, in my opinion, is when you want something like this: while m := f(many, args): # do stuff with m Now, if you're writing this in Python 3.7, you often end up with some code duplication: m = f(many, args) while m: # do stuff with m m = f(many, args) # duplicate Or something like this: while True: m = f(many, args) if not m: break # do stuff with m Personally, I consider the last version to be the most elegant, but still something of an anti-pattern. So I welcome this new operator.
- hermitdev 7y agoI literally wrote your last example twice last week to hash files in 4k chunks. Those 4 lines would be reduced to 1 with the walrus operator. I welcome it, as well. Edit: 1 line, not 2 (excluding the "do stuff")
- cellularmitosis 7y agoThis comes up in recursive descent implementations as well https://gist.github.com/cellularmitosis/53913ad229bad5d0a3bb92f196fdda7b https://gist.github.com/cellularmitosis/53913ad229bad5d0a3bb...
- ygrechuk 7y agoWhy not something like this: def f_iter(many, args): while True: m = f(many, args) if m: yield m else: raise StopIteration ... for m in f_iter(many, args): # do stuff with m This way you’re isolating all the initialization logic, error handling, etc. And you can focus on your domain logic in your client code.
- repsilat 7y agoAnd less reasonably, something similar for regular expressions: for m in filter([re.match(...)], bool): ... ;-)
- mlevental 7y agoso i should write a whole other function that iterates over a list instead of being happy that := exists?
- throwaway744678 7y agoYes! No pain, no gain!
- ciupicri 7y agoInstead of raising StopIteration you could also just return from the function. You could also make it a bit shorter if you swap the if branches. def f_iter(many, args): while True: m = f(many, args) if not m: return yield m
- Alex3917 7y agoIf you only have one or two regex like that then there is no difference in readability. If you have ten in a row though then suddenly your function no longer fits on the screen and it’s more difficult to understand what’s going on.
- bandrami 7y agoI mean, what you really want in this case is Perl's =~ operator, as well as the implicit $_ variable.
- eesmith 7y agoThink about if you match multiple patterns: for line in f: if (m:= pat1.search(line)) is not None: ... do stuff .. elif (m:= pat2.search(line)) is not None: ... do other stuff .. elif (m:= pat3.search(line)) is not None: ... do something else .. In older Python that's: for line in f: m = pat1.search(line) if m is not None: ... do stuff .. else: m = pat2.search(line) if m is not None: ... do other stuff .. else: m = pat3.search(line) if m is not None: ... do something else .. I think the newer makes it clear that it's supposed to be a simple elif chain, where all branches following the same structure. There are other ways to structure it, but the alternatives I can think of also have their own cumbersome complexities.
- kstrauser 7y agoThat's... lovely. I propose making this the standard example of why you might want to use the walrus operator.
- drdaeman 7y agoIf actions are different, I tend to put them in functions (independent testable pieces, yay!) and iterate over a mapping: LINE_ACTIONS = ( (re.compile("pattern 1"), do_stuff), (re.compile("pattern 2"), do_other_stuff), (re.compile("pattern 3"), do_something_else), ) ... for pattern, action in LINE_ACTIONS: m = pattern.search(line) if m is not None: action(m) break Or even use a method registry pattern that would auto-populate LINE_ACTIONS by just declaring the fuctions: @action("pattern 1") def do_stuff(m): ... Alternatively, I might just break the processing into a function: def _process(line): m = pat1.search(line) if m is not None: ... do stuff ... return m = pat2.search(line) if m is not None: ... do stuff ... return m = pat3.search(line) if m is not None: ... do stuff ... return _process(line) Of course, this depends on the purpose. Could be completely inadequate in some situations.
- eesmith 7y ago
- est 7y agoIMHO the whole `re.match` is a design flaw in stdlib. re.match() should always return a match object, but instead .group(1) will return None. Then we can write one-liners easier without the walrus operator.
- nine_k 7y agoSometimes you just check for a match (e.g.of entire string) and define no groups. The problem is not in re.match but in None being a relatively poor Maybe / Optional (still ways better than C-style `null` of many other languages).
- est 7y ago> Sometimes you just check for a match re.match vs re.search
- deleted 7y ago[deleted]
- Liquid_Fire 7y agore.search is different. re.match matches from the beginning of the string, while re.search matches from anywhere in the string.
- geofft 7y agoCould just have been if re.match(...).matched: But yeah, agree that Optional is what I want here. if let Some(m) := re.match(...) m.group(1)... m = re.match(...) m.group(1) # TypeError because I haven't unwrapped the Optional In current Python, that last line sometimes throws a runtime error.
- eesmith 7y agoI think you mean .group(0) as .group(1) can already return None: >>> import re >>> pat = re.compile("(A+)|(B+)") >>> m = pat.search("HUBBLE") >>> m.group(1) is None True >>> m.group(2) 'BB' >>> m.group(0) 'BB' I don't see how using your proposed change would help.
- xg15 7y agoI think there are a lot of use-cases for some kind of dedicated "if-with-outputs" syntax, where you have some extra variables available inside the if-block if the condition matched. Such a syntax could cover a lot of the problems of double execution but also prevent hard to understand code like the walrus operator. ... I've got no idea how the syntax could look though.
- Sean1708 7y agoHow would this hypothetical syntax differ from the walrus operator? I.e. what is hard to understand with the walrus operator that wouldn't be with this new hypothetical syntax and why?
- xg15 7y agoI think it could avoid cases where you "abuse" the walrus operator and make code harder to understand than necessary. E.g. things like: if (value := func()) and another_condition: ... Or: if another_condition or (value := func()): ... Or: x=1 my_list = [x := x+1, x := x-1] etc.
- Sean1708 7y agoAh ok, so you'd essentially allow it in fewer contexts? I would definitely have supported that, although I do think combining walrus with other boolean expressions in if and while is super useful and not particularly hard to understand.
- Elrac 7y agoLooking at your wish for exploratory evaluations in for-comprehensions, I was reminded of how Clojure does this. I found it quite elegant. The inside of the for-braces allow for keyword-based sub-clauses: There's - ":when" which will suppress output for elements not matching a filter expression, - ":while" which will stop when elements stop matching a filter, and - ":let" which will let you bind some new values in mid-loop. I rarely need these features, but when I do I find them really really helpful. Maybe someday someone will consider doing something similar in Python.
- kolinko 7y ago> while s := network_service.read(): > process(s) Watch out with this one though. In this case it's good, but if you receive numbers from such function, it will break loop not just on None but on 0 as well. Easy to forget. In many cases you'll need to do: while (s:= ....) is not None:
- hchasestevens 7y agoYour second use case is actually already covered in Python, assuming you have some kind of sentinel value you can break on: for s in iter(network_service.read, ''): process(s)
- mixmastamyk 7y agoIt comes up somewhat rarely but it is useful at times. But of those rare times, it is exceedingly rare to require any more complexity than you've shown above, such as having two assignments in one statement. That was the reasoning given for using ":=" instead of "as", to allow more complexity. I still think it was a mistake.