Re: [Python-ideas] Inline assignments using "given" clauses

I'd like to address Steven D'Aprano's reply earlier in the list concerning "given" vs ":=" clauses. My stance on the PEP is that the general ability to assign locally within an expression is undesirable. I gave several reasons, but in general, it means processing lines becomes more complex, and it makes refactoring more difficult. I don't think that there are enough cases where it would be useful to justify its negatives. I can recall but several occasions (writing regexes, mainly) that I've wanted something like this. However, if we were to assert that PEP 572 was mandatory, and then dispute its implementation, then I would argue as follows: *Cost:* A lot of people seem quite fixed upon the character cost of "given" vs ":=". I think this is a straw man. Most people spend longer reading than writing code, so if you agree with that observation, then it's the line length that you are concerned with. While it is true that overly long lines are not desirable, it's also true that incredibly short lines are equally difficult to understand. I don't think that keyword length can be independently taken too seriously as support for any proposal. Once you have to include grouping parentheses, this length difference quickly diminishes anyway. *Repetition of variable:* In addition, several people seem unhappy that the "given" keyword approach sees a repetition of the state variable in question. I think this is actually a benefit for two reasons. Firstly, you can save the expression in question to the intermediate variable, and then access a sub expression in a condition e.g while value.in_use given value = get_next_pool_item(): print(value.refcount()) Instead of while (value:=get_next_pool_item()) and value.in_use: print(value.refcount()) Secondly, it reads in the order that matters. When reading the first line, one encounters what the condition is evaluating *first*, and then the implementation details (m=p.match) second. It reads as one would describe a mathematical equation in a paper, and clearly separates *what you're interested in* from *what it depends upon*. This is what I particularly dislike about the ":=" operator approach, the value, and name it is bound to, are unrelated at the point of evaluation, yet are right next to each other. It's visual noise. In the example above, the reader has to read the entire line when trying to find the loop condition. Someone earlier suggested this was a bad thing, that the expression was far to the right of the line. I disagree. In cases where you might want to assign an expression to a variable, it is going to be used at least twice (otherwise just use the expression directly). At this point, the target name should be explicit in what it represents. This depends upon context slightly, such as what is happening in the local region of code. An example; when matching regexes, the match conditions (if x.group(2) == ...) are more important than what you matched on, usually. In addition, the two cases are not identical - if the API is such that get_next_pool_item should not return None values, this wouldn't be caught in the ":=" approach, unless you add more conditions. Yes, you could argue that I've cherry picked an example here. Actually, I haven't; I observed this after writing the example. What am I getting at here? In effect, the "given" keyword provides a superset of use cases to that of ":=". Dare I say it, but *explicit is better than implicit*. *Readability:* A smaller point is that I don't feel that ":=" is very readable. If we had to use an operator, I think $= is better, but me reasoning for this is weak. I think it derives from my observation that ":=" is slow to distinguish from "=". Regards, Angus Hollands On Fri, 11 May 2018 at 17:45 <python-ideas-request@python.org> wrote:

On Sat, May 12, 2018 at 7:12 AM, Angus Hollands <goosey15@gmail.com> wrote:
I'm not sure what you're getting at here. To make your two versions equivalent, you'd need to write them like this: while value and value.in_use given value = get_next_pool_item(): print(value.refcount()) while (value:=get_next_pool_item()) and value.in_use: print(value.refcount()) or like this: while value.in_use given value = get_next_pool_item(): print(value.refcount()) while (value:=get_next_pool_item()).in_use: print(value.refcount()) There, now they're identical. There's no superset or subset of use cases. And I have no idea how that connects with the oft-misused "explicit is better than implicit". (I'm still fairly sure that "explicit" and "strongly typed" are both synonyms for "stuff I like", with their antonyms "implicit" and "weakly typed" both being synonyms for "stuff I don't like". Years of discussion have not disproven this theory yet.) ChrisA

On Sat, May 12, 2018 at 07:44:24AM +1000, Chris Angelico wrote:
That's certainly how they are used the great majority of the time. A bit like how "strawman argument" is mostly used to mean "dammit, you just spotted an unwelcome consequence and/or flaw in my position which I have no counter too". :-) -- Steve

On 2018-05-11 22:12, Angus Hollands wrote:
while (value:=get_next_pool_item()) and value.in_use: print(value.refcount())
Just as a heads-up, I believe the prescribed way of doing that is: while (value := get_next_pool_item()).in_use: Of course you'd need additional mess to do something else with value. I don't like the asymmetry here: while (value := get_next_pool_item()).in_use and value is not blah:
I'm inclined to agree. But several people have argued that this is more readable than the alternative. I don't buy the reasoning, but they still like it better, and there's probably no point in going any further into this aspect. I doubt people are going to be convinced.
I'm not sure that it's strictly a superset. It's arguably the reverse, since it's restricted to statements with a condition rather than arbitrary expressions. I think the more important thing is that it's--subjectively--better at the subset of use cases that people seem to actually have (as listed in the OP).
Clearly the objectively best choice is "<-".

On 5/11/18 5:12 PM, Angus Hollands wrote:
:= would prevent you from using assignment expressions inside f-strings, which could be argued is a good thing. To demonstrate, and just for giggles, this works in 3.6, and appears to have the desired behavior: -------------------------- class X: def __init__(self, value): self.value = value def __str__(self): return str(self.value) def __format__(self, fmt): assert fmt[0] == '=' self.value = eval(fmt[1:]) return '' x = X(3) print(x) f'{x:=4}' # Behold! print(x) -------------------------- Produces: 3 4 I kid, of course. Eric

2018-05-12 11:36 GMT+03:00 Angus Hollands <goosey15@gmail.com>:
In addition, this form gives you all the advantages of tuple unpacking: while (x, y) > (5.0, 5.0) given x, y = get_neighbours(node): pass There was some criticism about the length of the `given`, maybe it is possible to _repurpose_ `with` keyword: while (x, y) > (5.0, 5.0) with x, y = get_neighbours(node): pass In this context, the with statement and with expression are clearly distinguishable both for the parser and for the person. But maybe many will find this as a bad style ... since the semantics are too different. With kind regards, -gdg

On Sat, May 12, 2018 at 7:12 AM, Angus Hollands <goosey15@gmail.com> wrote:
I'm not sure what you're getting at here. To make your two versions equivalent, you'd need to write them like this: while value and value.in_use given value = get_next_pool_item(): print(value.refcount()) while (value:=get_next_pool_item()) and value.in_use: print(value.refcount()) or like this: while value.in_use given value = get_next_pool_item(): print(value.refcount()) while (value:=get_next_pool_item()).in_use: print(value.refcount()) There, now they're identical. There's no superset or subset of use cases. And I have no idea how that connects with the oft-misused "explicit is better than implicit". (I'm still fairly sure that "explicit" and "strongly typed" are both synonyms for "stuff I like", with their antonyms "implicit" and "weakly typed" both being synonyms for "stuff I don't like". Years of discussion have not disproven this theory yet.) ChrisA

On Sat, May 12, 2018 at 07:44:24AM +1000, Chris Angelico wrote:
That's certainly how they are used the great majority of the time. A bit like how "strawman argument" is mostly used to mean "dammit, you just spotted an unwelcome consequence and/or flaw in my position which I have no counter too". :-) -- Steve

On 2018-05-11 22:12, Angus Hollands wrote:
while (value:=get_next_pool_item()) and value.in_use: print(value.refcount())
Just as a heads-up, I believe the prescribed way of doing that is: while (value := get_next_pool_item()).in_use: Of course you'd need additional mess to do something else with value. I don't like the asymmetry here: while (value := get_next_pool_item()).in_use and value is not blah:
I'm inclined to agree. But several people have argued that this is more readable than the alternative. I don't buy the reasoning, but they still like it better, and there's probably no point in going any further into this aspect. I doubt people are going to be convinced.
I'm not sure that it's strictly a superset. It's arguably the reverse, since it's restricted to statements with a condition rather than arbitrary expressions. I think the more important thing is that it's--subjectively--better at the subset of use cases that people seem to actually have (as listed in the OP).
Clearly the objectively best choice is "<-".

On 5/11/18 5:12 PM, Angus Hollands wrote:
:= would prevent you from using assignment expressions inside f-strings, which could be argued is a good thing. To demonstrate, and just for giggles, this works in 3.6, and appears to have the desired behavior: -------------------------- class X: def __init__(self, value): self.value = value def __str__(self): return str(self.value) def __format__(self, fmt): assert fmt[0] == '=' self.value = eval(fmt[1:]) return '' x = X(3) print(x) f'{x:=4}' # Behold! print(x) -------------------------- Produces: 3 4 I kid, of course. Eric

2018-05-12 11:36 GMT+03:00 Angus Hollands <goosey15@gmail.com>:
In addition, this form gives you all the advantages of tuple unpacking: while (x, y) > (5.0, 5.0) given x, y = get_neighbours(node): pass There was some criticism about the length of the `given`, maybe it is possible to _repurpose_ `with` keyword: while (x, y) > (5.0, 5.0) with x, y = get_neighbours(node): pass In this context, the with statement and with expression are clearly distinguishable both for the parser and for the person. But maybe many will find this as a bad style ... since the semantics are too different. With kind regards, -gdg
participants (6)
-
Angus Hollands
-
Chris Angelico
-
Ed Kellett
-
Eric V. Smith
-
Kirill Balunov
-
Steven D'Aprano