6 ms·
I simply do not understand how the walrus operator is harder to read. Maybe an example? my_match = regex.match(foo) if my_match: return my_matc
by DangitBobby 5y ago
I simply do not understand how the walrus operator is harder to read. Maybe an example?
my_match = regex.match(foo)
if my_match:
return my_match.groups()
# continues with the now useless my_match in scope
Versus
if my_match := regex.match(foo):
return my_match.groups()
# continues without useless my_match in scope
How is the second one less readable? Have you ever heard of a real world example of a beginner or literally anyone ever actually expressing confusion over this?
- dataflow 5y agoThe problem isn't that simple use case. Although even in that case, they already had '=' as an assignment operator, and they could've easily kept it like the majority of other languages do instead of introducing an inconsistency. The more major problem with the walrus operator is more complicated expressions they made legal with it. Like, could you explain to me why making these legal was a good thing? def foo() return ... def bar(): yield ... while foo() or (w := bar()) < 10: # w is in-scope here, but possibly nonexistent! # Even in C++ it would at least *exist*! print(w) # The variable is still in-scope here, and still *nonexistent* # Ditto as above, but even worse outside the loop print(w := w + 1) If they just wanted your use case, they could've made only expressions of the form 'if var := val' legal, and maybe the same with 'while', not full-blown assignments in arbitrary expressions, which they had (very wisely) prohibited for decades for the sake of readability. And they would've scoped the variable to the 'if', not made it accessible after the conditional. But nope, they went ahead and just did what '=' does in any language, and to add insult to injury, they didn't even keep the existing syntax when it has exactly the same meaning. And it's not like they even added += and -= and all those along with it (or +:= and -:= because apparently that's their taste) to make it more useful in that direction, if they really felt in-expression assignments were useful, so it's not like you get those benefits either.
- DangitBobby 5y agoIn your example, if you leave out the parentheses around w := bar(), you get "SyntaxError: cannot use assignment expressions with operator" which makes me think it's a bug in the interpreter and not intentionally designed to allow it. I am baffled to learn that it's kept in scope outside of the statement it's assigned, and I agree it would have a negative impact on readability if used outside of the if statement.
- dataflow 5y ago> if you leave out the parentheses around w := bar(), you get "SyntaxError: cannot use assignment expressions with operator" which makes me think it's a bug in the interpreter and not intentionally designed to allow it. No, I'm pretty sure that's intentional. You want the left-hand side of an assignment to be crystal clear, which "foo() or w := bar()" is not. It looks like it's assigning to (foo() or w).
- DangitBobby 5y agoTo be clear: def thing(): return True if thing() or w:= "ok": # SyntaxError: cannot use assignment expressions with operator pass print(w) . . . if thing() or (w := "ok"): pass print(w) # NameError: name 'w' is not defined The first error makes me think your concern (that w is conditionally undefined) was anticipated and supposed to be guarded against with the SyntaxError. I believe the fact you can bypass it with parentheses is a bug and not an intentional design decision.
- dataflow 5y agoOh I see, you're looking at it from that angle. But no, it's intentional. Check out PEP 572 [1]: > The motivation for this special case is twofold. First, it allows us to conveniently capture a "witness" for an any() expression, or a counterexample for all(), for example: if any((comment := line).startswith('#') for line in lines): print("First comment:", comment) else: print("There are no comments") I have a hard time believing even the authors (let alone you) could tell me with a straight face that that's easy to read. If they really believe that, I... have questions about their experiences. The beauty of Python... [1] https://www.python.org/dev/peps/pep-0572/ https://www.python.org/dev/peps/pep-0572/
- DangitBobby 5y agoYour new example makes me wonder: if I can intentionally conditionally bring variables into existence with the walrus operator, what's the motivation behind the SyntaxError in my statement above? I maintain my belief that the real issue here is, readability aside, if blocks do not implement a new scope, which has always been a problem in the language. The walrus operator just gives you new ways to trip over that problem. From the PEP: > An assignment expression does not introduce a new scope. In most cases the scope in which the target will be bound is self-explanatory: it is the current scope. If this scope contains a nonlocal or global declaration for the target, the assignment expression honors that. A lambda (being an explicit, if anonymous, function definition) counts as a scope for this purpose. I find this particularly strange and inconsistent: lines = ["1"] [(comment := line).startswith('#') for line in lines] print(comment) # 1 [x for x in range(3)] print(x) # NameError: name 'x' is not defined
- eesmith 5y agoWhile the walrus operator gives a way to see this sort of non-C++ behavior, it's more showing that Python isn't C++ than something special about the operator. Here's another way to trigger the same NameError, via "global": import random def foo(): return random.randrange(2) def bar(): global w w = return random.randrange(20) return w while foo() or (bar() < 10): print(w) For even more Python-is-not-C++-fun: import re def parse_str(s): def m(pattern): # I <3 Perl! nonlocal _ _ = re.match(pattern, s) return _ is not None if m("Name: (.*)$"): return ("name", _[1]) if m("State: (..) City: (.*)$"): return ("city", (_[2], _[1])) if m(r"ZIP: (\d{5})(-(\d{4}))?$"): return ("zip", _[1] + (_[2] if _[2] else "")) return ("Unknown", s) del _ # Remove this line and the function isn't valid Python(!) for line in ( "Name: Ernest Hemingway", "State: FL City: Key West", "ZIP: 33040", ): print(parse_str(line))
- dataflow 5y agoRight, I'm quite well-aware of that, but I'm saying this change has made the situation even worse. If they ensured the variables were scoped and actually initialized it'd have actually been an improvement.
- eesmith 5y agoI apologize. I misinterpreted your comment: # w is in-scope here, but possibly nonexistent! # Even in C++ it would at least *exist*! because I don't see how bringing up C++'s semantics is relevant when Python has long raised an UnboundLocalError for similar circumstances. If I understand you correctly, you believe Python should have introduced scoping so the "w" would be valid only in the if, elif, and else clauses, and not after the 'if' ends. This would be similar to how the error object works in the 'except' clause: >>> try: ... 1/0 ... except Exception as err: ... err = "Hello" ... >>> err Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'err' is not defined If so, I do not have the experience or insight to say anything meaningful.
- pansa2 5y agoThe walrus operator provides no benefit here. `my_match` is still in scope in both cases. Python’s `if` statements do not introduce a new scope.
- DangitBobby 5y agoI know they don't, normally. I really thought that was basically the point of the walrus operator to begin with, that the variable was only in scope for the lifetime of the if statement where it's needed. Huge bummer to find out that's not true.
- dataflow 5y agoLooks like now you're seeing why it's an abomination ;)
- DangitBobby 5y agoIMO the real abomination was already present in the language, which is that if blocks do not introduce new scope. My IDE protects me from the bugs this could easily introduce when I try to use a variable that may not yet be in scope, but it should be detected before runtime. I will readily admit that the walrus operator doesn't do what I thought it did and I have no interest in whatever utility it provides as it exists today.
- dataflow 5y ago> IMO the real abomination was already present in the language, which is that if blocks do not introduce new scope. Definitely. You would think if they're going to undermine decades of their own philosophy, they would instead introduce variable declarations and actually help mitigate some bugs in the process.
- slightwinder 5y agoScoop in python is normally defined by functions/methods, not blocks. The same happens with for-loos and with-blocks. So this is consistent. And this good, because it can be very useful. The exception here are try/except-blocks, where the fetched error is cleaned up after leaving the except-block, for reasons.
- Linosaurus 5y agoThere's this example from an explanation linked elsewhere in the discussion. The relevant parts of the if statement are now much further separated. if (percentage := ((student["marks"]/700)*100)) > 70:
- IlliOnato 5y agoI don't know how important this is, but I believe it does make it less readable for "outsiders". As a non-Python programmer it is usually pretty easy for me to correctly guess what a piece of Python code does. (And once in a while I need to take a look at some Python code). Walrus operator got me. I tried to guess what it did, but even having simple code examples I could not. My guesses were along the lines of binding versus plain assignment, or some such. None of my guesses were even close. I had to google it to find out (of course I could also read the documentation).