On Sat, 18 Jun 2022 at 00:21, Paul Moore <p.f.moore@gmail.com> wrote:
On Fri, 17 Jun 2022 at 14:15, Chris Angelico <rosuav@gmail.com> wrote:
There are several ways to make this clearly sane.
# Clearly UnboundLocalError def frob(n=>len(items), items=>[]):
Um, I didn't see that as any more obvious than the original example. I guess I can see it's UnboundLocalError, but honestly that's not obvious to me.
Question: Is this obvious? def f(): x, x[0] = [2], 3 print(x) def boom(): x[0], x = 3, [2] # raises UnboundLocalError I understand that left-to-right evaluation is something that has to be learned (and isn't 100% true - operator precedence is a thing too), but at very least, if it isn't *obvious*, it should at least be *unsurprising* if you then get UnboundLocalError.
# Clearly correct behaviour def frob(items=[], n=>len(items)): def frob(items=>[], n=>len(items)):
Maybe... I'm not sure I see this as *that* much more obvious, although I concede that the left-to-right evaluation rule implies it (it feels like a mathematician's use of "obvious" - which quite often isn't ;-)) Using assignment expressions in argument defaults is well-defined but not necessarily obvious in a similar way (to me, at least).
When you say "assignment expressions", do you mean "default expressions", or are you referring to the walrus operator? There's a lot of other potentially-surprising behaviour if you mix assignment expressions in with this, because of the difference of scope. It's the sort of thing that can definitely be figured out, but I would advise against it. def frob(items=>[], n=>len(items:=[])): This will reassign items to be an empty list if n is omitted. Obviously that's bad code, but in general, I think assignment expressions inside default expressions are likely to be very surprising :)
The only way for it to be confusing is to have => on one argument and then = on a subsequent argument, *and* to have the earlier one refer to the later one.
For you, maybe. I assert that the forms above *are* confusing for me. You're welcome to explain them to me, like you have, and maybe I'll now remember the logic for the future, but as a data point, I stand by my statement that these were confusing to me when I encountered them fresh.
Then let's leave aside the term "obvious" and just go for "unsurprising". If you write code and get UnboundLocalError, will you be surprised that it doesn't work? If you write code and it works, will you be surprised with the result you got? Once you learn the basic idea of left-to-right evaluation, it should be possible to try things out and get unsurprising results. That's what I'm hoping for.
Feel free to state that there's not *enough* cases of people being confused by the semantics to outweigh the benefits, but it feels to me that there are a few people claiming confusion here, and simply saying "you shouldn't be confused, it's obvious" isn't really addressing the point.
Part of the problem is that one person seems to think that Python will completely change its behaviour, and he's spreading misinformation. Ignore him, look just at the proposal itself, and tell me if it's still confusing.
Even if someone *can* provide an answer, I'd be reluctant to accept that any answer could be described as "intuitive". And "well, don't do that" is just ducking the question - in essentially the same way as "it's implementation defined" does...
But "don't do that" is a perfectly reasonable response to other kinds of bad code, like messing up your spacing:
x = 1+2 * 3+4
Is this intuitive? Some people will think that x should be 21, but the actual answer is 11. Python won't stop you from doing this, but style guides absolutely should.
But that's not the same as you leaving the behaviour implementation defined. In the case of operator precedence, there *is* a well-defined answer, but the spacing doesn't match that interpretation.
That IS the concern when people are talking about what's "intuitive" though.
But in the case of
frob(n=>len(items), items=())
you're refusing to give a well-defined semantics, and then saying that people shouldn't do that. But unlike spacing of expressions, the order of arguments is *important* - it is part of the API of frob that the first positional argument is n, so "just swap the arguments" is a semantic change. So how should people get the ("obvious") intended behaviour? Abandon the new syntax and go back to using None as a default? That seems a shame, given that (as I understand it) your reference implementation works exactly as I'd want.
The only two possible behaviours are: 1) It does the single obvious thing: n defaults to the length of items, and items defaults to an empty tuple. 2) It raises UnboundLocalError if you omit n. To be quite honest, I can't think of any non-toy examples where the defaults would be defined backwards, like this. (Keyword-only arguments can of course be reordered as required, as their order isn't part of the API.) But if there is one out there, then yes, you would probably need to go back to using None; or, you can rely on implementation-specific permission and do it anyway. It's not like Steven's constant panic-fear that "undefined behaviour" literally means the Python interpreter could choose to melt down your computer. There are *two* options, no more, no less, for what is legal.
In the same way, I would strongly recommend that style guides frown upon referring to arguments later in the parameter list, even if it happens to be legal. I'm just not mandating that the language check for this and artificially block it.
You're not *just* recommending this for style guides, you're also explicitly stating that you refuse to assign semantics to it.
It's unfair to say that I "refuse to assign semantics" as if I'm permitting literally any behaviour. All I'm doing is saying that the UnboundLocalError is optional, *at this stage*. There have been far less-defined semantics that have remained in the language for a long time, or cases where something has changed in behaviour over time despite not being explicitly stated as implementation-defined. Is this legal? def f(): x = 1 global x Does Python mandate whether this is legal or not? If so, how far back in Python's history has it been defined? The semantics, if this code is legal, are obvious: the name x must always refer to the global, including in the assignment above it. If it's not legal, you get an exception, not an interpreter crash, not your hard drive getting wiped, and not a massive electric shock to the programmer. Would you prefer that I simply mandate that it be permitted, and then a future version of Python changes it to be an exception? Or the other way around? Because I could do that. Maybe it would reduce the arguments. Pun intended, and I am not apologizing for it. ChrisA