Re: [Python-Dev] PEP 572: Assignment Expressions

On Sun, Apr 22, 2018 at 3:13 PM, Steve Dower <steve.dower@python.org> wrote:
This example makes me want “if expr as name:” (same semantics as ‘with’, and the name is always bound to the expression result regardless of truthiness), but doesn’t move me on assignment expressions.
In reality there often are other conditions being applied to the match for which `if expr as name` is inadequate. The simplest would be something like if ...: <something> elif (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2): <whatever> And the match() call may not even be the first thing to check -- e.g. we could have elif line is not None and (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2): -- --Guido van Rossum (python.org/~guido)

[Guido]
In reality there often are other conditions being applied to the match for which `if expr as name` is inadequate. The simplest would be something like
if ...: <something> elif (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2): <whatever>
And the match() call may not even be the first thing to check -- e.g. we could have
elif line is not None and (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2):
I find myself warming more to binding expressions the more I keep them in mind while writing new code. And I think it may be helpful to continue showing real examples where they would help. Today's example: I happened to code this a few hours ago: diff = x - x_base if diff: g = gcd(diff, n) if g > 1: return g It's not really hard to follow, but two levels of nesting "feels excessive", as does using the names "diff" and "g" three times each. It's _really_ an "and" test: if the diff isn't 0 and gcd(diff, n) > 1, return the gcd. That's how I _thought_ of it from the start. Which this alternative expresses directly: if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g That's so Pythonic I could cry ;-)

On Mon, 23 Apr 2018 00:44:44 -0500 Tim Peters <tim.peters@gmail.com> wrote:
Which this alternative expresses directly:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
That's so Pythonic I could cry ;-)
It looks like C to me. That won't make me cry (I write C++ code daily these days), but it's certainly not the same language as Python. The second part, especially, where you use the result of an assignment expression as a comparison operand, looks definitely un-Pythonic. Regards Antoine.

[Tim]
Which this alternative expresses directly:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
That's so Pythonic I could cry ;-)
[Antoine]
It looks like C to me. That won't make me cry (I write C++ code daily these days), but it's certainly not the same language as Python.
The second part, especially, where you use the result of an assignment expression as a comparison operand, looks definitely un-Pythonic.
You snipped the part explaining _what's_ "Pythonic" about it: It's _really_ an "and" test: if the diff isn't 0 and gcd(diff, n) > 1, return the gcd. That's how I _thought_ of it from the start. "Expresses directly" is the Pythonic part; the syntax is minor to me. Seeing that the _intent_ is an "and test" is a pattern-matching puzzle in the original spelling (which essentially turned me into a compiler, writing low-level code for the _concepts_ I had in mind from the start): diff = x - x_base if diff: g = gcd(diff, n) if g > 1: return g But note that the part of the PEP I support is just the "binding expression" part: giving a simple name (binding an identifier) to the result of an expression. I don't want the full potential complexity of assignment statements in expressions. There's nothing "un-Pythonic" about merely giving a name to an expression result, apart from that there are few contexts that currently support that in a sanely usable way.

On Mon, Apr 23, 2018 at 8:28 AM, Antoine Pitrou <solipsis@pitrou.net> wrote:
On Mon, 23 Apr 2018 00:44:44 -0500 Tim Peters <tim.peters@gmail.com> wrote: [...]
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g That's so Pythonic I could cry ;-)
[...]
The second part, especially, where you use the result of an assignment expression as a comparison operand, looks definitely un-Pythonic.
Which, I suppose, underlines that Pythonicity is in the mind of the beholder.
The assignment expression seems like a vary natural way to introduce variables of limited (controlled?) scope, and the class-namespace special case doesn't seem horrible enough to put me, at least, off the idea. There will, of course, be those who abuse assignment expressions, and I'm very much looking forward to seeing what David Beazley makes of them. While Tim's expression might look (superficially) like C, the five-line alternative isn't exactly an inspiring example of Pythonicity, is it? regards Steve

On 23.04.2018 17:59, Steve Holden wrote:
While Tim's expression might look (superficially) like C, the five-line alternative isn't exactly an inspiring example of Pythonicity, is it?
What about diff = x - x_base if diff and gcd(diff, n) > 1: return gcd(diff, n) # or if (x - x_base) and gcd(x - x_base, n) > 1: return gcd(x - x_base, n) and have the interpreter handle the optimization, or apply an lru_cache? ;-) Cheers, Sven

On Tue, Apr 24, 2018 at 3:13 AM, Sven R. Kunze <srkunze@mail.de> wrote:
On 23.04.2018 17:59, Steve Holden wrote:
While Tim's expression might look (superficially) like C, the five-line alternative isn't exactly an inspiring example of Pythonicity, is it?
What about
diff = x - x_base if diff and gcd(diff, n) > 1: return gcd(diff, n)
# or
if (x - x_base) and gcd(x - x_base, n) > 1: return gcd(x - x_base, n)
and have the interpreter handle the optimization, or apply an lru_cache? ;-)
And then you want to change something, and you have to make an edit in two places. Or, worse, you make it in only one of those places, they become desynchronized, and nobody can figure out why the program occasionally and bizarrely fails. Removing duplicate function calls isn't just about run-time performance. ChrisA

On 23.04.2018 19:24, Chris Angelico wrote:
On Tue, Apr 24, 2018 at 3:13 AM, Sven R. Kunze <srkunze@mail.de> wrote:
diff = x - x_base if diff and gcd(diff, n) > 1: return gcd(diff, n)
# or
if (x - x_base) and gcd(x - x_base, n) > 1: return gcd(x - x_base, n)
and have the interpreter handle the optimization, or apply an lru_cache? ;-) And then you want to change something, and you have to make an edit in two places. Or, worse, you make it in only one of those places, they become desynchronized, and nobody can figure out why the program occasionally and bizarrely fails.
If you change any of those lines (including ones of my fore-posters) without knowing what you're doing, you'd better don't touch them at all. The SQL folks btw. are pretty okay'ish with this kind of duplication because they can resolve it. Surely, Python isn't SQL but sometimes I wish Python could handle such things as easily without me having to babysit it all the time and using Perl'ish syntax (which := looks like to me). We then have :=, = and ==. Sorry, but Python wouldn't fit my brain then.

On Tue, Apr 24, 2018 at 6:21 AM, Sven R. Kunze <srkunze@mail.de> wrote:
On 23.04.2018 19:24, Chris Angelico wrote:
On Tue, Apr 24, 2018 at 3:13 AM, Sven R. Kunze <srkunze@mail.de> wrote:
diff = x - x_base if diff and gcd(diff, n) > 1: return gcd(diff, n)
# or
if (x - x_base) and gcd(x - x_base, n) > 1: return gcd(x - x_base, n)
and have the interpreter handle the optimization, or apply an lru_cache? ;-)
And then you want to change something, and you have to make an edit in two places. Or, worse, you make it in only one of those places, they become desynchronized, and nobody can figure out why the program occasionally and bizarrely fails.
If you change any of those lines (including ones of my fore-posters) without knowing what you're doing, you'd better don't touch them at all.
The SQL folks btw. are pretty okay'ish with this kind of duplication because they can resolve it. Surely, Python isn't SQL but sometimes I wish Python could handle such things as easily without me having to babysit it all the time and using Perl'ish syntax (which := looks like to me). We then have :=, = and ==. Sorry, but Python wouldn't fit my brain then.
Ah, are you one of those programmers who writes code once and it's instantly perfect? I apologize, I didn't realize I was in the presence of a unicorn. Most programmers will end up making edits to all non-trivial code. And if your code is laid out in such a way that edits are easier and safer, you're less likely to introduce bugs as you edit. Duplication works against that by forcing you to make changes in two places. I've seen code that relies on duplication and compiler optimizations. Sure, it'll run just as fast as the equivalent with actual variable names; but that's beside the point. It takes extra effort to maintain such code, and that is what matters. ChrisA

On 23.04.2018 22:37, Chris Angelico wrote:
Ah, are you one of those programmers who writes code once and it's instantly perfect? I apologize, I didn't realize I was in the presence of a unicorn.
Wow, constructive. Nothing is perfect, but if you don't consider your surroundings when doing changes, well, what could possibly go wrong...
Duplication works against that by forcing you to make changes in two places.
... in the very same line, a line below, few characters after/before it.
I've seen code that relies on duplication and compiler optimizations. Sure, it'll run just as fast as the equivalent with actual variable names; but that's beside the point. It takes extra effort to maintain such code, and that is what matters.
That's exactly my point. Readability is what counts, especially for maintaining. "gcd(diff, n)" is a great name, much better than "g", if you ask me. We aren't talking about 1000 lines of code here. The new syntax will enable one-liner optimizations. And I think Tim's example is as good as we can get realistically. Because if the expressions become longer or more complex and/or the numbers of expressions increase, I doubt that a majority want to have that in a single line even though the syntax would allow this. And if so the editor might include some line wraps, so then we are where we were before. Somewhere, you need to get your hands dirty.

On Tue, Apr 24, 2018 at 7:25 AM, Sven R. Kunze <srkunze@mail.de> wrote:
On 23.04.2018 22:37, Chris Angelico wrote:
Ah, are you one of those programmers who writes code once and it's instantly perfect? I apologize, I didn't realize I was in the presence of a unicorn.
Wow, constructive. Nothing is perfect, but if you don't consider your surroundings when doing changes, well, what could possibly go wrong...
I've seen way too many programmers - myself included - think that synchronized edits are viable. If you are the one and only perfect programmer, then sure! But if you're like the rest of us, it's better to build your code in such a way that it doesn't bite you. Yes, it was a snarky way to make the point, but I have a strong point to make. ChrisA

[Sven R. Kunze <srkunze@mail.de>]
What about
diff = x - x_base if diff and gcd(diff, n) > 1: return gcd(diff, n)
# or
if (x - x_base) and gcd(x - x_base, n) > 1: return gcd(x - x_base, n)
and have the interpreter handle the optimization, or apply an lru_cache? ;-)
Surely you're joking. This is math.gcd(), which is expensive for multi-thousand bit integers, and the interpreter knows nothing about it. Adding a cache of _any_ kind (LRU or otherwise) would make it even slower (in the application, there's no reason to expect that x - x_base will repeat a value before O(sqrt(n)) iterations, which itself can be thousands of bits - a cache hit would be a miracle).

On 23.04.2018 19:31, Tim Peters wrote:
Surely you're joking. This is math.gcd(), which is expensive for multi-thousand bit integers, and the interpreter knows nothing about it. Adding a cache of _any_ kind (LRU or otherwise) would make it even slower. Alright, if that problem is just about performance, then there must be a better way to resolve it rather than inventing a new syntax. Btw. storing the result in a local var is also a cache IMHO. And if gcd is immutable, I think Python can do a great job here of optimizing.
Anyway, your example is the best one I've seen so far.

[Tim]
Surely you're joking. This is math.gcd(), which is expensive for multi-thousand bit integers, and the interpreter knows nothing about it. Adding a cache of _any_ kind (LRU or otherwise) would make it even slower.
[Sven R. Kunze <srkunze@mail.de>]
Alright, if that problem is just about performance,
It's not, but others had already pointed out that it's generally considered Poor Practice (and for good reasons) to textually repeat expressions, so I didn't echo that. Even in purely functional languages, where textually equal snippets are guaranteed to evaluate to the same result every time, "give these expressions these brief names" constructs are heavily used (see, .e.g, "let" and "where" in Haskell).
:then there must be a better way to resolve it rather than inventing a new syntax.
Why? "Give the result of an expression a name" is already heavily used in Python - it's just that the _contexts_ in which it can be done are very limited now.
Btw. storing the result in a local var is also a cache IMHO. And if gcd is immutable, I think Python can do a great job here of optimizing.
After decades, CPython still does nothing of the sort, short of having eventually made, e.g., "None" and "True" and "False" reserved words so at least it can optimize uses of those. It knows nothing at all about which library functions are pure - and there's no code in the implementation currently capable of exploiting such information even if it were known. That remains a fantasy in CPython.
Anyway, your example is the best one I've seen so far.
Guido gave better ones, where binding expressions would allow to collapse arbitrarily deep levels of nesting to just one (if ... elif ... elif ... elif ...). My example only eliminated a single level of artificial indentation. But my example did have the advantage of being taken verbatim from actual, working code ;-)

On 23.04.2018 23:41, Tim Peters wrote:
Why? "Give the result of an expression a name" is already heavily used in Python - it's just that the _contexts_ in which it can be done are very limited now.
Which is a good thing IMO. It keeps stuff simple to reason about. At least to me, the objective of an expression is to generate a result, not to introduce new names. YMMV.
After decades, CPython still does nothing of the sort, short of having eventually made, e.g., "None" and "True" and "False" reserved words so at least it can optimize uses of those. It knows nothing at all about which library functions are pure - and there's no code in the implementation currently capable of exploiting such information even if it were known. That remains a fantasy in CPython.
CPython optimizes on dicts pretty heavily and on a lot of other things as well. Victor, e.g., is pretty prolific here when it comes to optimizing things for the 95% usecases. Maybe, considering pure functions might be another step.
Guido gave better ones, where binding expressions would allow to collapse arbitrarily deep levels of nesting to just one (if ... elif ... elif ... elif ...). My example only eliminated a single level of artificial indentation. But my example did have the advantage of being taken verbatim from actual, working code ;-)
I like the example, but not the solution it proposes. gcd(diff, n) is to me a perfect name, and please don't tell me g is better. ;) To me it seems, that many people consider function_of_parameter a better name than function(parameter). IMO it isn't. I've seen lots of code where people do things like foo_of_bar = bar['foo']. Because they don't want another dict access, or they think it better reflects the concept. But it does not as does not g. Writing gcd(diff, n) twice is not more repeating as is writing foo_of_bar twice. Because gcd is a pure function, gcd(diff, n) is just a synonym/name of the very same number. That it needs to be calced twice, is a detail, like a double dict access. In the end, it's only optimizing what could a be done by a machine much more reliably. Though, people now want to save that extra assignment line (and indentations) via syntax, because they do that kind of optimizing and want to do it by hand for some reason. Just my 2 cents to let you see where I'm coming from.

On Tue, Apr 24, 2018 at 10:42:35PM +0200, Sven R. Kunze wrote:
gcd(diff, n) is to me a perfect name, and please don't tell me g is better. ;)
Sorry, gcd(diff, n) is not the "perfect name", and I will tell you that sometimes g is better. Which would you prefer to see and read? g = gcd(diff, n) result = [g, g+2, 3*g, g**5] or: result = [gcd(diff, n), gcd(diff, n)+2, 3*gcd(diff, n), gcd(diff, n)**5] Forget about the inefficiency of calculating the same result over and over again. Just think about reading the code, especially aloud. If you were describing the code to a colleague, would you really repeat the phrase "gcd of diff, n" *four* separate times? I don't know about you, but I wouldn't, and I wouldn't want to read it four separate times. Think about having to edit it. Would you rather edit it in one place or four? Even close together, in a single expression, it is annoying to have "gcd(diff, n)" repeated over and over again.
To me it seems, that many people consider function_of_parameter a better name than function(parameter). IMO it isn't.
Of course not. But in some contexts, p is a much better name than function(parameter). In other contexts, something more descriptive will be a better name: page_count = (len(document.chapter[previous_chapter].pages()) + len(document.chapter[this_chapter].pages())) page_count is a MUCH better name than repeating (len(document.chapter[previous_chapter].pages()) + len(document.chapter[this_chapter].pages())) over and over again, even if it is a pure function.
I've seen lots of code where people do things like foo_of_bar = bar['foo']. Because they don't want another dict access, or they think it better reflects the concept. But it does not as does not g. Writing gcd(diff, n) twice is not more repeating as is writing foo_of_bar twice. Because gcd is a pure function
You might know that, but how does somebody reading the code know which functions are pure and which are not? How does the compiler know? It's easy to say that you recognise gcd as a pure function. How about len(document.chapter[this_chapter].pages()), is that a pure function? -- Steve

On 25.04.2018 01:19, Steven D'Aprano wrote:
Sorry, gcd(diff, n) is not the "perfect name", and I will tell you that sometimes g is better. [...]
We were talking about the real-world code snippet of Tim (as a justification of := ) and alternative rewritings of it without resorting to new syntax. Not about "sometimes", or in some toy examples, or code without unknown life-time and intention (interactive, library, etc).
You might know that, but how does somebody reading the code know which functions are pure and which are not? How does the compiler know?
There are invisible optimizations in CPython already. And for the most obvious examples, humans don't need to know. It's like wondering if constant folding really works. It's the same with all optimizations: if it works, it's fine. If it cannot be made working, the original code still works and you can optimize by hand anyway. The two obvious ways: the compiler might have a predefined list of pure functions OR the code tells him with an annotation of any sort. Both ways work, the latter somehow feels cleaner.
It's easy to say that you recognise gcd as a pure function. How about len(document.chapter[this_chapter].pages()), is that a pure function?
What exactly do you want me to answer? An algorithm? A trained neural network? Usually, one starts with the low-hanging fruits and extends if possible and needed. So that would be way out of scope.

On Fri, Apr 27, 2018 at 3:33 AM, Sven R. Kunze <srkunze@mail.de> wrote:
On 25.04.2018 01:19, Steven D'Aprano wrote:
Sorry, gcd(diff, n) is not the "perfect name", and I will tell you that sometimes g is better. [...]
We were talking about the real-world code snippet of Tim (as a justification of := ) and alternative rewritings of it without resorting to new syntax. Not about "sometimes", or in some toy examples, or code without unknown life-time and intention (interactive, library, etc).
You're asking for some massive changes in semantics, though. If you want to discuss that, it's nothing at all to do with PEP 572, and is a completely new proposal.
You might know that, but how does somebody reading the code know which functions are pure and which are not? How does the compiler know?
There are invisible optimizations in CPython already. And for the most obvious examples, humans don't need to know. It's like wondering if constant folding really works. It's the same with all optimizations: if it works, it's fine. If it cannot be made working, the original code still works and you can optimize by hand anyway. The two obvious ways: the compiler might have a predefined list of pure functions OR the code tells him with an annotation of any sort. Both ways work, the latter somehow feels cleaner.
An annotation? You mean like functools.lru_cache? That's technically nothing to do with pure functions, but it's about as much as you're likely to get. The function still has to be called, and the result has to be cached somewhere. Ultimately, "pure function optimization" is just another form of cache. Please, take this to python-ideas, or even better, to python-list. It's not a counter-argument to PEP 572. ChrisA

On Thu, Apr 26, 2018 at 10:33 AM, Sven R. Kunze <srkunze@mail.de> wrote:
On 25.04.2018 01:19, Steven D'Aprano wrote:
Sorry, gcd(diff, n) is not the "perfect name", and I will tell you that sometimes g is better. [...]
We were talking about the real-world code snippet of Tim (as a justification of := ) and alternative rewritings of it without resorting to new syntax.
Apologies if this idea has already been discussed (I might have missed the relevant email), but thinking back to Tim's earlier example-- if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g it occurs to me this could be implemented with current syntax using a pattern like the following: stashed = [None] def stash(x): stashed[0] = x return x if stash(x - x_base) and stash(gcd(stashed[0], n)) > 1: return stashed[0] There are many variations to this idea, obviously. For example, one could allow passing a "name" to stash(), or combine stash / stashed into a single, callable object that allows setting and reading from its store. I wonder if one of them could be made into a worthwhile pattern or API.. --Chris

On Tue, May 1, 2018 at 3:36 AM, Chris Jerdonek <chris.jerdonek@gmail.com> wrote:
On Thu, Apr 26, 2018 at 10:33 AM, Sven R. Kunze <srkunze@mail.de> wrote:
On 25.04.2018 01:19, Steven D'Aprano wrote:
Sorry, gcd(diff, n) is not the "perfect name", and I will tell you that sometimes g is better. [...]
We were talking about the real-world code snippet of Tim (as a justification of := ) and alternative rewritings of it without resorting to new syntax.
Apologies if this idea has already been discussed (I might have missed the relevant email), but thinking back to Tim's earlier example--
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
it occurs to me this could be implemented with current syntax using a pattern like the following:
stashed = [None]
def stash(x): stashed[0] = x return x
if stash(x - x_base) and stash(gcd(stashed[0], n)) > 1: return stashed[0]
There are many variations to this idea, obviously. For example, one could allow passing a "name" to stash(), or combine stash / stashed into a single, callable object that allows setting and reading from its store. I wonder if one of them could be made into a worthwhile pattern or API.. .
I hope you don't think this recasting, is in any way less confusing to a beginner than an inline assignment. This is language abuse! In any case, what advantages would it have over simply declaring "stashed" as a global inside the function and omitting the confusing subscripting? regards Steve

On Tue, May 1, 2018 at 2:14 AM, Steve Holden <steve@holdenweb.com> wrote:
On Tue, May 1, 2018 at 3:36 AM, Chris Jerdonek <chris.jerdonek@gmail.com> wrote:
On Thu, Apr 26, 2018 at 10:33 AM, Sven R. Kunze <srkunze@mail.de> wrote:
On 25.04.2018 01:19, Steven D'Aprano wrote:
Sorry, gcd(diff, n) is not the "perfect name", and I will tell you that sometimes g is better. [...]
We were talking about the real-world code snippet of Tim (as a justification of := ) and alternative rewritings of it without resorting to new syntax.
Apologies if this idea has already been discussed (I might have missed the relevant email), but thinking back to Tim's earlier example--
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
it occurs to me this could be implemented with current syntax using a pattern like the following:
stashed = [None]
def stash(x): stashed[0] = x return x
if stash(x - x_base) and stash(gcd(stashed[0], n)) > 1: return stashed[0]
There are many variations to this idea, obviously. For example, one could allow passing a "name" to stash(), or combine stash / stashed into a single, callable object that allows setting and reading from its store. I wonder if one of them could be made into a worthwhile pattern or API..
I hope you don't think this recasting, is in any way less confusing to a beginner than an inline assignment. This is language abuse!
I didn't make any claims that it wouldn't be confusing (especially as is). It was just an _idea_. I mentioned it because (1) it uses current syntax, (2) it doesn't require intermediate assignments or extra indents in the main body of code, (3) it doesn't even require choosing intermediate names, and (4) I didn't see it mentioned in any of the previous discussion. All three of the first points have been major sources of discussion in the thread. So I thought it might be of interest.
In any case, what advantages would it have over simply declaring "stashed" as a global inside the function and omitting the confusing subscripting?
Right. Like I said, there are many variations. I just listed one to convey the general idea. --Chris

Sven R. Kunze wrote:
if (x - x_base) and gcd(x - x_base, n) > 1: return gcd(x - x_base, n)
and have the interpreter handle the optimization, or apply an lru_cache? ;-)
Problem is, there's no way to automatically tell whether the expressions involved have side effects that are being relied on. -- Greg

What facilities does the interpreter currently have for extracting common subexpressions, and how would it verify in such a dynamic environment that such extractions wouldn't alter the semantics of the program? Explicit (assignment using :=) is better than implicit (by optimizations hidden to the programmer). regards Steve Steve Holden On Mon, Apr 23, 2018 at 6:13 PM, Sven R. Kunze <srkunze@mail.de> wrote:
On 23.04.2018 17:59, Steve Holden wrote:
While Tim's expression might look (superficially) like C, the five-line alternative isn't exactly an inspiring example of Pythonicity, is it?
What about
diff = x - x_base if diff and gcd(diff, n) > 1: return gcd(diff, n)
# or
if (x - x_base) and gcd(x - x_base, n) > 1: return gcd(x - x_base, n)
and have the interpreter handle the optimization, or apply an lru_cache? ;-)
Cheers, Sven
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/ steve%40holdenweb.com

On Mon, 23 Apr 2018 16:59:35 +0100 Steve Holden <steve@holdenweb.com> wrote:
On Mon, Apr 23, 2018 at 8:28 AM, Antoine Pitrou <solipsis@pitrou.net> wrote:
On Mon, 23 Apr 2018 00:44:44 -0500 Tim Peters <tim.peters@gmail.com> wrote: [...]
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g That's so Pythonic I could cry ;-)
[...]
The second part, especially, where you use the result of an assignment expression as a comparison operand, looks definitely un-Pythonic.
Which, I suppose, underlines that Pythonicity is in the mind of the beholder.
Indeed it is. What we can only say is that the proposed idiom goes against current Python syntactical rules :-)
The assignment expression seems like a vary natural way to introduce variables of limited (controlled?) scope, [...]
AFAIU, the scope isn't limited to the "if" block, it's a regular local variable. I might have misread.
While Tim's expression might look (superficially) like C, the five-line alternative isn't exactly an inspiring example of Pythonicity, is it?
I don't know. I've written my share of similar-looking code and I've never really been bothered, at least not enough that I thought we should change the language to accomodate those use cases. To be frank I don't remember the last time I was bothered by an aspect of Python's syntax. I think the language has reached "peak syntax" by now and it should basically be frozen, and any improvements targeted at other aspects (semantics, performance, stdlib, etc.). Regardless, my three questions about this are: - does it make Python more powerful? - does it make Python more readable? - does it make Python easier to learn and teach? My answer would be "no" to all three, but YMMV. Regards Antoine.

[Steve Holden <steve@holdenweb.com>]
... The assignment expression seems like a vary natural way to introduce variables of limited (controlled?) scope, [...]
[Antoine Pitrou <solipsis@pitrou.net>]
AFAIU, the scope isn't limited to the "if" block, it's a regular local variable. I might have misread.
You're right about the current version of the PEP. No new scoping rules are introduced. The PEP does suggest some changes to corner case scoping semantics, though.
... Regardless, my three questions about this are: - does it make Python more powerful?
Goodness no.
- does it make Python more readable?
There are cases where it would, and cases where it wouldn't. People shouldn't use it in the latter cases ;-) I very recently wrote this block of code: outside = p2units[p1][tgt_kind] - units[2] if outside: if not all(self.crossout(q, n, undo) for q in outside): return False The opening pair is a very common minor annoyance; it's marginally more readable like so: if outside := p2units[p1][tgt_kind] - units[2]: Saving an essentially useless line with a duplicated name is worth something to me, because it comes up so very often. But that's indeed "minor". In my diff/gcd example, it reduced 5 lines of code to 2; saved a level of annoying (semantically misleading) indentation; and cut the number of instances of both "diff" and "g" from 3 each to 2 each (ideal: one each to bind the name, and then one each to use the name later). That's relatively substantial by any measure. In Guido's if/elif/elif/elif/elif ... complex text processing example template, it can save an unbounded number of semantically needless indentation levels. So the readability benefits can range from highly negative to highly positive.
- does it make Python easier to learn and teach?
By whom? Almost no addition has ever made a language easier to learn for raw beginners: every addition is something they eventually need to learn. We could make Python easier to learn for beginners by throwing out virtually everything added since version 0.9.6 ;-) But people coming _from_ most other very widely used languages (C, C++, Java, Javascript, Perl, ...) are already familiar with assignment expressions. The limited (to a plain identifier target) "binding expression" PEP simplification I favor would be nothing new to them at all (whereas the full complexity of Python's assignment statements is indeed beyond what they're used to, but needs to be taught & learned regardless of this PEP's fate). At least when restricted to binding expressions, the syntax is simple and the semantics are the very simplest case of what people (convert or raw beginner) need to learn for Python's assignment statements regardless.
My answer would be "no" to all three, but YMMV.
And it did ;-)

On Tue, 24 Apr 2018 01:06:30 -0500 Tim Peters <tim.peters@gmail.com> wrote:
- does it make Python easier to learn and teach?
By whom? Almost no addition has ever made a language easier to learn for raw beginners: every addition is something they eventually need to learn. We could make Python easier to learn for beginners by throwing out virtually everything added since version 0.9.6 ;-)
Constructs like "with ..." or "try / except / finally" make the language easier to learn compared to the dances they are meant to replace. "await" is a more readable and less confusing improvement over "yield from". Format strings dispense from the older, more convoluted formulations. Iteration is much simpler than the longer forms we would have to write if generalized iterators didn't exist. Regards Antoine.

On Tue, Apr 24, 2018 at 4:27 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
On Tue, 24 Apr 2018 01:06:30 -0500 Tim Peters <tim.peters@gmail.com> wrote:
- does it make Python easier to learn and teach?
By whom? Almost no addition has ever made a language easier to learn for raw beginners: every addition is something they eventually need to learn. We could make Python easier to learn for beginners by throwing out virtually everything added since version 0.9.6 ;-)
Constructs like "with ..." or "try / except / finally" make the language easier to learn compared to the dances they are meant to replace. "await" is a more readable and less confusing improvement over "yield from". Format strings dispense from the older, more convoluted formulations. Iteration is much simpler than the longer forms we would have to write if generalized iterators didn't exist.
And assignment expressions are far simpler than breaking things out over multiple lines (particularly when you consider the infinite while loop alternative). But that doesn't change the fact that, as features, they do make the language harder to understand. The difference is that they make *your program* easier to understand. If you really want the *language* to be as easy as possible to fit into your head, you want a Turing tarpit: https://esolangs.org/wiki/Ook! Only three lexical tokens, and only eight operations. But any program written in Ook is going to be almost completely unreadable. That's the tradeoff. ChrisA

On Tue, 24 Apr 2018 16:38:39 +1000 Chris Angelico <rosuav@gmail.com> wrote:
On Tue, Apr 24, 2018 at 4:27 PM, Antoine Pitrou <solipsis@pitrou.net> wrote:
On Tue, 24 Apr 2018 01:06:30 -0500 Tim Peters <tim.peters@gmail.com> wrote:
- does it make Python easier to learn and teach?
By whom? Almost no addition has ever made a language easier to learn for raw beginners: every addition is something they eventually need to learn. We could make Python easier to learn for beginners by throwing out virtually everything added since version 0.9.6 ;-)
Constructs like "with ..." or "try / except / finally" make the language easier to learn compared to the dances they are meant to replace. "await" is a more readable and less confusing improvement over "yield from". Format strings dispense from the older, more convoluted formulations. Iteration is much simpler than the longer forms we would have to write if generalized iterators didn't exist.
And assignment expressions are far simpler than breaking things out over multiple lines (particularly when you consider the infinite while loop alternative). But that doesn't change the fact that, as features, they do make the language harder to understand.
I did specifically say "easier to learn and teach" instead of using generic phrases such as "simpler" or "harder to understand". You won't make Python easier to learn and teach by adding a new assignment syntax, since people will have to learn the old form as well. Having to break things out over multiple lines is a fact of life, if only for readability when implementing (and maintaining!) non-trivial processing routines. It's a good thing to be used to it, and to learn to choose good names for intermediate variables. Regards Antoine.

[Antoine Pitrou <solipsis@pitrou.net>]
... Having to break things out over multiple lines is a fact of life, if only for readability when implementing (and maintaining!) non-trivial processing routines. It's a good thing to be used to it, and to learn to choose good names for intermediate variables.
Well, the last part is overselling: by its very nature, a binding expression does not relieve the programmer one whit from needing to pick good names. The name is part of the binding expression. The sheer number of names needed is the same with or without binding expressions, although the latter allow for less repetitive typing (& reading) of those names. For the rest, _needing_ to split a simple bind-and-test across two lines doesn't really build character, or have any other virtue (besides familiarity to old-time Python programmers) I can see. Neither does falling into indentation hell have any virtue in the rarer cases where binding expressions really shine. Simple things _should_ be simple to do; indeed, when they are, that's an incentive to keep things simple. There will still be plenty of code where splitting multiple bindings across multiple lines is obviously better.

[Antoine]
- does it make Python easier to learn and teach?
[Tim]
By whom? Almost no addition has ever made a language easier to learn for raw beginners: every addition is something they eventually need to learn. We could make Python easier to learn for beginners by throwing out virtually everything added since version 0.9.6 ;-)
[Antoine]
Constructs like "with ..." or "try / except / finally" make the language easier to learn compared to the dances they are meant to replace.
They nevertheless need to be taught & learned (and try/except/finally was essentially always in the language), You snipped the parts pointing out that binding expressions are already familiar to people coming from most other languages, and even for raw beginners the semantics are the tiniest part of what they need to learn anyway about Python's assignment expressions. So that was my point: they don't make Python any harder to learn or teach. To the contrary, for people coming from other languages, it's one less thing they're used to they wouldn't have to _un_learn.
"await" is a more readable and less confusing improvement over "yield from".
Heh. Not to me. I have literally have no idea what to with "await" (I use generators heavily, but have had no use yet for coroutines), but use yield from an_iterable routinely. That use is perfectly clear, to the point that it _can't_ be improved on: it already does exactly what I want from it, with no effort at all. It's simply impossible that, whatever "await" does, it could be more readable or less confusing than what I use "yield from" for.
Format strings dispense from the older, more convoluted formulations.
But they didn't _replace_ them. They're Yet Another Way to Format Strings everyone has to learn. That made teaching/learning harder, not easier, but you can make a case they make Python easier to _use_ for people who eschew the older forms.
Iteration is much simpler than the longer forms we would have to write if generalized iterators didn't exist.
I'll buy that one. Now go through the HISTORY file and count all the changes you didn't name ;-)

On Tue, 24 Apr 2018 01:55:13 -0500 Tim Peters <tim.peters@gmail.com> wrote:
[Antoine]
Constructs like "with ..." or "try / except / finally" make the language easier to learn compared to the dances they are meant to replace.
They nevertheless need to be taught & learned (and try/except/finally was essentially always in the language), You snipped the parts pointing out that binding expressions are already familiar to people coming from most other languages
Yes... I think most will agree that Python is generally easy to take up for people coming from C++ etc., so my "easier to learn and teach" was mostly about non-programmers.
even for raw beginners the semantics are the tiniest part of what they need to learn anyway about Python's assignment expressions.
I'm not sure what you mean by that. If it's the tiniest part, what's the overwhelming part? Is the new assigment expression that delicate to use that it requires reading a long and intimidating design document ? I didn't get that impression, so it seems you may be making a stronger point than me for rejeting the PEP :-)
"await" is a more readable and less confusing improvement over "yield from".
Heh. Not to me. I have literally have no idea what to with "await" (I use generators heavily, but have had no use yet for coroutines), but use
yield from an_iterable
routinely.
Yeah... "yield from" is fine for that, except that it was explicitly meant for the coroutine use case as well (I'm not sure what the timeline is, but probably Guido was already thinking/dreaming about tulip/asyncio back then). And trying to shoehorn both in a single construct made it confusing and inadequate. When you want to express two abstractly different concepts (generating a stream of values, or suspending a task until some asynchronous subtask finishes), it makes things easier if those two concepts have two different concrete expressions. Hence "await" making the language easier to learn for those whose use cases benefit from it. To bring another example: the fact that Python has separate syntax to declare classes and functions makes it easier to learn "Python with classes" than "Javascript with classes", even though the raw _grammar_ is made slightly more complex by it. In Javascript you have to learn the weird (ab)use of functional notation for the purpose of declaring object behaviour, and learn to recognize it when you read it. In Python there's the "class" notation which, despite adding a keyword to remember, makes class declaration easier to learn and master. (^^ note this example is about a potentially obsolete dialect of Javascript; perhaps it has class notation nowadays? ^^)
It's simply impossible that, whatever "await" does, it could be more readable or less confusing than what I use "yield from" for.
Probably because "await" wouldn't work at all for you, then :-)
Format strings dispense from the older, more convoluted formulations.
But they didn't _replace_ them. That made teaching/learning harder, not easier,
Intuitively, it sounds easier to teach f'some {value}' rather than either the .format() or %-formatting alternatives. The whole goal of f-strings, after all, is to make string formatting more approachable. Learning a language is not learning the whole spec. When you learn C, you don't need to learn the oddities of pre-ANSI function declarations :-) However, assignment a special case in this regard, since traditional assignment is so omnipresent in online resources, that people _will_ encounter it even if they make a very focused use of Python.
Iteration is much simpler than the longer forms we would have to write if generalized iterators didn't exist.
I'll buy that one. Now go through the HISTORY file and count all the changes you didn't name ;-)
You claimed that """almost no addition has ever made a language easier to learn for raw beginners""". I claim that several additions did (for Python alone), but I don't need to prove that most of them did ;-) Regards Antoine.

[Antoine]
... Yes... I think most will agree that Python is generally easy to take up for people coming from C++ etc., so my "easier to learn and teach" was mostly about non-programmers.
[Tim]
even for raw beginners the semantics are the tiniest part of what they need to learn anyway about Python's assignment expressions.
I'm not sure what you mean by that. If it's the tiniest part, what's the overwhelming part?
I was hoping it was clear from context that I was talking about "binding expressions", not the PEP's wholly general "assignment expressions".
Is the new assigment expression that delicate to use that it requires reading a long and intimidating design document ? I didn't get that impression, so it seems you may be making a stronger point than me for rejecting the PEP :-)
I'm -1 myself on the PEP's assignment expressions, because there are no compelling use cases yet for any but the simplest ("binding expressions") cases. And, yes, understanding Python's assignment statements is challenging. Just understanding their grammar is challenging: assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression) target_list ::= target ("," target)* [","] target ::= identifier | "(" [target_list] ")" | "[" [target_list] "]" | attributeref | subscription | slicing | "*" target Followed by pages of dense text explaining what all those possibilities mean. A binding expression is more like: binding_expression ::= identifier ":=" expression and the only part of the assignment statement docs needed to explain the meaning is the brief "If the target is an identifier (name)" section, augmented with "and the value of `expression` is the value of the binding expression". If someone has learned what i = 1 means, they already learned almost all of what binding expressions mean too. The target in a binding expression can't be more complicated than the `i` in that example.
"await" is a more readable and less confusing improvement over "yield from".
Heh. Not to me. I have literally have no idea what to with "await" (I use generators heavily, but have had no use yet for coroutines), but use
yield from an_iterable
routinely.
Yeah... "yield from" is fine for that, except that it was explicitly meant for the coroutine use case as well (I'm not sure what the timeline is, but probably Guido was already thinking/dreaming about tulip/asyncio back then). And trying to shoehorn both in a single construct made it confusing and inadequate.
When you want to express two abstractly different concepts (generating a stream of values, or suspending a task until some asynchronous subtask finishes), it makes things easier if those two concepts have two different concrete expressions. Hence "await" making the language easier to learn for those whose use cases benefit from it.
All of which I remain blissfully unaware of :-) ...
It's simply impossible that, whatever "await" does, it could be more readable or less confusing than what I use "yield from" for.
Probably because "await" wouldn't work at all for you, then :-)
I'm glad people who need "await" got it - they'd have to pry _my_ uses of "yield from" from my cold, dead fingers ;-) Despite that all my uses could be trivially replaced by for _ in an_iterable: yield _ "yield from" saves typing, indentation, and conceptual noise for me. It's the "binding expressions" of nested generators ;-)
Format strings dispense from the older, more convoluted formulations.
But they didn't _replace_ them. That made teaching/learning harder, not easier,
Intuitively, it sounds easier to teach f'some {value}' rather than either the .format() or %-formatting alternatives. The whole goal of f-strings, after all, is to make string formatting more approachable.
Learning a language is not learning the whole spec. When you learn C, you don't need to learn the oddities of pre-ANSI function declarations :-)
A difference is that there still are mountains of code using earlier string formatting methods, and my guess is that there always will be. f-strings aren't always "better". For example, any number of generators (including the combinatoric generators from itertools) yield a sequence of tuples, and format_string % a_tuple is often the simplest way to format the tuple components. Breaking the tuple apart first, whether via explicit indexing in an f-string, or via unpacking into a tuple of names for use in an f-string, is often needless complication. So % formatting needs to be learned by anyone who wants to read _other_ peoples' code. Then again, that's fine by me, because I don't really care whether something new needs to be learned. What I do care about is whether the benefits exceed the costs of learning.
However, assignment a special case in this regard, since traditional assignment is so omnipresent in online resources, that people _will_ encounter it even if they make a very focused use of Python.
Which means absolutely everyone already understands almost everything about the semantics of binding expressions ;-)
Iteration is much simpler than the longer forms we would have to write if generalized iterators didn't exist.
I'll buy that one. Now go through the HISTORY file and count all the changes you didn't name ;-)
You claimed that """almost no addition has ever made a language easier to learn for raw beginners""". I claim that several additions did (for Python alone), but I don't need to prove that most of them did ;-)
Nor do I need to prove that "almost no" is substantially smaller than "several" ;-)

On Wed, Apr 25, 2018 at 2:40 AM, Tim Peters <tim.peters@gmail.com> wrote:
[Antoine]
... Yes... I think most will agree that Python is generally easy to take up for people coming from C++ etc., so my "easier to learn and teach" was mostly about non-programmers.
[Tim]
even for raw beginners the semantics are the tiniest part of what they need to learn anyway about Python's assignment expressions.
I'm not sure what you mean by that. If it's the tiniest part, what's the overwhelming part?
I was hoping it was clear from context that I was talking about "binding expressions", not the PEP's wholly general "assignment expressions".
Hopefully you have seen, or soon will see, the latest posting of the PEP, in which assignment targets are restricted to simple names. :) Though I still talk about "assignment expressions". I don't see a problem with calling them that, but I also don't see a problem with calling them "binding expressions" if you prefer. ChrisA

[Chris Angelico <rosuav@gmail.com>]
Hopefully you have seen, or soon will see, the latest posting of the PEP, in which assignment targets are restricted to simple names. :)
I haven't yet, but look forward to it! You have the patience of a saint to endure all this - I would have given up 6 years ago ;-)
Though I still talk about "assignment expressions". I don't see a problem with calling them that, but I also don't see a problem with calling them "binding expressions" if you prefer.
It's psychology ;-) So long as the PEP calls them assignment expressions, people are going to imagine facing the horrors of things like the current *b, c = a[c] = a assignment statement buried deep inside expressions. But in conventional use, "binding" is restricted to identifiers, which vastly simplifies the mental model for "the worst" that can happen. Since fear is the most potent motivator, "don't scare people" is rule #1 ;-) But, in the absence of Guido chiming in, it's really up to you. A few people have expressed positive feelings about changing the name to "binding expressions", and none opposed it (that I saw), but the sample size is too small to claim that "proves" anything.

On 25 April 2018 at 03:01, Tim Peters <tim.peters@gmail.com> wrote:
assignment statement buried deep inside expressions. But in conventional use, "binding" is restricted to identifiers, which vastly simplifies the mental model for "the worst" that can happen.
[snip]
But, in the absence of Guido chiming in, it's really up to you. A few people have expressed positive feelings about changing the name to "binding expressions", and none opposed it (that I saw), but the sample size is too small to claim that "proves" anything.
I go back and forth, as I suspect even if we call them "name binding expressions" in the language reference (which I think would be a good idea), they'll often be referred to colloquially as "assignment expressions". That's probably OK though - list displays and dict displays are regularly referred to as literals, and the meaning remains clear, even though the use of literal is technically inaccurate. I also think it would be good for the PEP to spell out the following semantic invariant for code running in a regular namespace: _rhs = expr assert (target := _rhs) is _rhs and target is _rhs It's the restriction to single names as targets that makes it possible to impose such a strong assertion about what the syntax means. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Tue, Apr 24, 2018 at 8:24 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I also think it would be good for the PEP to spell out the following semantic invariant for code running in a regular namespace:
_rhs = expr assert (target := _rhs) is _rhs and target is _rhs
It's the restriction to single names as targets that makes it possible to impose such a strong assertion about what the syntax means.
Can you elaborate? I don't understand what you mean by this. -- --Guido van Rossum (python.org/~guido)

On 25 April 2018 at 13:56, Guido van Rossum <guido@python.org> wrote:
On Tue, Apr 24, 2018 at 8:24 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I also think it would be good for the PEP to spell out the following semantic invariant for code running in a regular namespace:
_rhs = expr assert (target := _rhs) is _rhs and target is _rhs
It's the restriction to single names as targets that makes it possible to impose such a strong assertion about what the syntax means.
Can you elaborate? I don't understand what you mean by this.
The assertion is just spelling out in code form that given the name binding expression "target := expr", then: 1. the result of the expression itself is "expr", exactly as if the name binding was omitted 2. that result is also bound to the name "target" in the current scope The preceding "_rhs = expr" line is included to make the invariant generalise even to expressions that have side effects or can change their output based on mutable system state. Ironically, that may be clearer if I use another assignment statement to break it out as two separate invariants: _rhs = expr _inline_result = (bound_name := _rhs) assert _inline_result is _rhs assert bound_name is _rhs By contrast, the protocols involved in handling more complex assignment targets and in handling augmented assignment mean that it wouldn't be possible to define a similarly simple invariant of what they mean (since the exact runtime behaviour would be both type and target dependent). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Wed, Apr 25, 2018 at 6:15 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 25 April 2018 at 13:56, Guido van Rossum <guido@python.org> wrote:
On Tue, Apr 24, 2018 at 8:24 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I also think it would be good for the PEP to spell out the following semantic invariant for code running in a regular namespace:
_rhs = expr assert (target := _rhs) is _rhs and target is _rhs
It's the restriction to single names as targets that makes it possible to impose such a strong assertion about what the syntax means.
Can you elaborate? I don't understand what you mean by this.
The assertion is just spelling out in code form that given the name binding expression "target := expr", then:
1. the result of the expression itself is "expr", exactly as if the name binding was omitted 2. that result is also bound to the name "target" in the current scope
The preceding "_rhs = expr" line is included to make the invariant generalise even to expressions that have side effects or can change their output based on mutable system state.
Ironically, that may be clearer if I use another assignment statement to break it out as two separate invariants:
_rhs = expr _inline_result = (bound_name := _rhs) assert _inline_result is _rhs assert bound_name is _rhs
By contrast, the protocols involved in handling more complex assignment targets and in handling augmented assignment mean that it wouldn't be possible to define a similarly simple invariant of what they mean (since the exact runtime behaviour would be both type and target dependent).
While it's handy that one _could_ use any valid assignment target, allowing this wouldn't (IMHO) necessarily be a good idea. Binding/assignment expressions fit well into Python's semantics (where bindings copy references rather than data) precisely because names are effectively and straightforwardly shorthand for values at the point of assignment. Restricting the targets in target := expression to simple names would avoid a lot of the tricksiness that less experienced programmers might be tempter to indulge, leading to simpler code without undue restrictions on what can be done. The PEP is currently somewhat confused on naming, since it is entitled "Assignment Expressions" but appears to then exclusively use the name "named expressions" to reference the concept under "Syntax and Semantics" and "assignment expression" elsewhere. I'd prefer the term "name binding expressions," since that implies the stricture that more complex targets are excluded. Whatever is chosen, usage in the PEP should be consistent. regards Steve

On 04/22/2018 10:44 PM, Tim Peters wrote:
[Guido]
In reality there often are other conditions being applied to the match for which `if expr as name` is inadequate. The simplest would be something like
if ...: <something> elif (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2): <whatever>
And the match() call may not even be the first thing to check -- e.g. we could have
elif line is not None and (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2):
I find myself warming more to binding expressions the more I keep them in mind while writing new code. And I think it may be helpful to continue showing real examples where they would help.
Today's example: I happened to code this a few hours ago:
diff = x - x_base if diff: g = gcd(diff, n) if g > 1: return g
It's not really hard to follow, but two levels of nesting "feels excessive", as does using the names "diff" and "g" three times each. It's _really_ an "and" test: if the diff isn't 0 and gcd(diff, n) > 1, return the gcd. That's how I _thought_ of it from the start.
Which this alternative expresses directly:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
So I really like being able to make the assignment in the expression, but I have a really hard time parsing it with the name first. Attempting to read just the names first: if diff scan for ending right paren found and g scan for ending right paren oops, found opening left paren scan for ending right paren found resume scanning for final right paren found > 1: return g Attempting to read expressions first: if x - x_base and gcd(diff, n) what's diff? scan backwards diff is x - x_base > 1: return g what's g? scan up and backwards g is gcd(diff, n) Attempting to read interleaved: if skip diff x - x_base back to diff as diff and skip g gcd(diff, n) back to g as g
1: return g
On the other hand, if it were using the "as" keyword: if (x - xbase as diff) and (gcd(diff, n) as g) > 1: return g I would parse as: if x - x_base as diff and gcd(diff, n) as g
1: return g
For me at least, the last is much more readable. Thinking about it some more, the problem (or maybe just my problem) is that I see an "if" or "while" and the I look for the thing that is True or False, and using the ":=" syntax the first thing I see is a placeholder for a result that doesn't exist yet, making me constantly scan backwards and forwards to put all the pieces in the correct place. With "as", it just flows forwards. -- ~Ethan~

On Apr 23, 2018, at 13:01, Ethan Furman <ethan@stoneleaf.us> wrote:
On 04/22/2018 10:44 PM, Tim Peters wrote:
I find myself warming more to binding expressions the more I keep them in mind while writing new code.
And I really like the term “binding expressions” because that’s how I think about this feature. I also think it will be easier to explain because “all it does” is bind a value to a name, and to me that’s the most powerful and valuable thing behind this feature.
So I really like being able to make the assignment in the expression, but I have a really hard time parsing it with the name first.
Me too. Plus we *already* have precedence for spelling name bindings in similar constructs, such as import statements, with statements, and exceptions. It seems like a natural and Pythonic approach to extend that same spelling to binding expressions rather than introducing new, weird, symbols. I also think it effectively solves the switch-statement problem: if (get_response() as answer) == 'yes': do_it() elif answer == 'no': skip_it() elif answer == 'maybe' okay_then() That’s Pythonic enough for jazz! -Barry

On 23.04.2018 23:19, Barry Warsaw wrote:
I also think it effectively solves the switch-statement problem:
if (get_response() as answer) == 'yes': do_it() elif answer == 'no': skip_it() elif answer == 'maybe' okay_then()
That’s Pythonic enough for jazz!
Is it just me or since when has the following Python code fallen out of favor? answer = get_response() if answer == 'yes': do_it() elif answer == 'no': skip_it() elif answer == 'maybe' okay_then() I really don't see anything I would want to optimize here. Not even a single bit. But as said that might just be me. What I like about this code is: 1) symmetry: all ifs are equally structured 2) classic IPO model: first get input, then process (, then output) Sven

On 2018-04-23, 21:34 GMT, Sven R. Kunze wrote:
Is it just me or since when has the following Python code fallen out of favor?
It isn’t just you. Matěj -- https://matej.ceplovi.cz/blog/, Jabber: mcepl@ceplovi.cz GPG Finger: 3C76 A027 CA45 AD70 98B5 BC1D 7920 5802 880B C9D8 … one of the main causes of the fall of the Roman Empire was that, lacking zero, they had no way to indicate successful termination of their C programs. -- Robert Firth

On 2018-04-23 14:19, Barry Warsaw wrote:
Me too. Plus we *already* have precedence for spelling name bindings in similar constructs, such as import statements, with statements, and exceptions. It seems like a natural and Pythonic approach to extend that same spelling to binding expressions rather than introducing new, weird, symbols.
We've survived for decades without this syntax, so don't think the need is so great that we need to rush it. Let's not "jump the shark." :D In my opinion, "EXPR as NAME" is the only version worthy enough for the readability of Python. It reads like psuedo-code, as advocates have described in the past, and already used frequently in a fuzzy, non-dogmatic manner. The point about the potential of a bug in the "with" statement is worth serious consideration however. I don't have a problem with it, but if deemed intolerable there was an additional solution I read upthread, believe by Kirill and Steve. Merely that, since all current use cases are in the if/while/comprehension statements, it might be a good idea to limit binding-expressions there to avoid issues/overuse elsewhere. Well, there are a number of mitigations to the "with" issue that could be considered. (Hoping that this is my last post on the subject.) -Mike

[Ethan Furman <ethan@stoneleaf.us>]
So I really like being able to make the assignment in the expression, but I have a really hard time parsing it with the name first.
...
On the other hand, if it were using the "as" keyword:
if (x - xbase as diff) and (gcd(diff, n) as g) > 1: return g
I would parse as:
if x - x_base as diff and gcd(diff, n) as g
1: return g
For me at least, the last is much more readable. Thinking about it some more, the problem (or maybe just my problem) is that I see an "if" or "while" and the I look for the thing that is True or False, and using the ":=" syntax the first thing I see is a placeholder for a result that doesn't exist yet, making me constantly scan backwards and forwards to put all the pieces in the correct place.
With "as", it just flows forwards.
I can read it fine either way, and don't much care. A possible advantage of an "as" operator is that its precedence could be set to bind just a tad stronger than comparisons (which include "is" and "is not" in Python), and then, e.g., if f() as result is not None: do something with result could work as intended. So long as people can't get "assignment _statements_" out of their heads, if result := f() is not None: groups instead as if result := (f() is not None): which would almost never be _intended_. Maybe spelling it "as" instead could break that. However, against "as" is that its current use in "with" statements does something quite different: with f() as name: does not bind the result of `f()` to `name`, but the result of `f().__enter__()`. Whether that "should be" fatal, I don't know, but it's at least annoying ;-)

On 04/23/2018 03:04 PM, Tim Peters wrote:
[Ethan Furman <ethan@stoneleaf.us>]
So I really like being able to make the assignment in the expression, but I have a really hard time parsing it with the name first.
...
On the other hand, if it were using the "as" keyword:
if (x - xbase as diff) and (gcd(diff, n) as g) > 1: return g
I would parse as:
if x - x_base as diff and gcd(diff, n) as g
1: return g
For me at least, the last is much more readable. Thinking about it some more, the problem (or maybe just my problem) is that I see an "if" or "while" and the I look for the thing that is True or False, and using the ":=" syntax the first thing I see is a placeholder for a result that doesn't exist yet, making me constantly scan backwards and forwards to put all the pieces in the correct place.
With "as", it just flows forwards.
I can read it fine either way, and don't much care. A possible advantage of an "as" operator is that its precedence could be set to bind just a tad stronger than comparisons (which include "is" and "is not" in Python), and then, e.g.,
if f() as result is not None: do something with result
could work as intended. So long as people can't get "assignment _statements_" out of their heads,
if result := f() is not None:
groups instead as
if result := (f() is not None):
which would almost never be _intended_. Maybe spelling it "as" instead could break that.
However, against "as" is that its current use in "with" statements does something quite different:
with f() as name:
does not bind the result of `f()` to `name`, but the result of `f().__enter__()`. Whether that "should be" fatal, I don't know, but it's at least annoying ;-)
"as" does something slightly different in each of its current incantations, but the one thing that they all have in common is taking some thing on the left and giving it the name on the right: - imports -> thing on left gets name on right - exceptions -> exception that matches class on left gets name on right - with -> result of left.__enter__() gets name on right I see very little harm in adding - expression-binding -> eval(thing on left) gets name on right -- ~Ethan~

Whereas I find it a deal-breaker for 'as'. On Mon, Apr 23, 2018 at 3:15 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
On 04/23/2018 03:04 PM, Tim Peters wrote:
[Ethan Furman <ethan@stoneleaf.us>]
So I really like being able to make the assignment in the expression, but I have a really hard time parsing it with the name first.
...
On the other hand, if it were using the "as" keyword:
if (x - xbase as diff) and (gcd(diff, n) as g) > 1: return g
I would parse as:
if x - x_base as diff and gcd(diff, n) as g
1: return g
For me at least, the last is much more readable. Thinking about it some more, the problem (or maybe just my problem) is that I see an "if" or "while" and the I look for the thing that is True or False, and using the ":=" syntax the first thing I see is a placeholder for a result that doesn't exist yet, making me constantly scan backwards and forwards to put all the pieces in the correct place.
With "as", it just flows forwards.
I can read it fine either way, and don't much care. A possible advantage of an "as" operator is that its precedence could be set to bind just a tad stronger than comparisons (which include "is" and "is not" in Python), and then, e.g.,
if f() as result is not None: do something with result
could work as intended. So long as people can't get "assignment _statements_" out of their heads,
if result := f() is not None:
groups instead as
if result := (f() is not None):
which would almost never be _intended_. Maybe spelling it "as" instead could break that.
However, against "as" is that its current use in "with" statements does something quite different:
with f() as name:
does not bind the result of `f()` to `name`, but the result of `f().__enter__()`. Whether that "should be" fatal, I don't know, but it's at least annoying ;-)
"as" does something slightly different in each of its current incantations, but the one thing that they all have in common is taking some thing on the left and giving it the name on the right:
- imports -> thing on left gets name on right - exceptions -> exception that matches class on left gets name on right - with -> result of left.__enter__() gets name on right
I see very little harm in adding
- expression-binding -> eval(thing on left) gets name on right
-- ~Ethan~
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/guido% 40python.org
-- --Guido van Rossum (python.org/~guido)

On Apr 23, 2018, at 18:04, Tim Peters <tim.peters@gmail.com> wrote:
However, against "as" is that its current use in "with" statements does something quite different:
with f() as name:
does not bind the result of `f()` to `name`, but the result of `f().__enter__()`. Whether that "should be" fatal, I don't know, but it's at least annoying ;-)
Prior art: COBOL uses "GIVING", as in: ADD x, y GIVING z No need to re-invent the wheel ;) -- Ned Deily nad@python.org -- []

Using 'as' was debated extensively on python-ideas. I don't like it for several reasons: - the semantics are subtly different from all other uses of 'as' in Python; I'd like to reserve 'as' for "not a plain assignment" - a short word is easier to miss when skimming code than punctuation - most other languages (Java*, C*) borrow from assignment (name = expr) On Mon, Apr 23, 2018 at 3:19 PM, Ned Deily <nad@python.org> wrote:
On Apr 23, 2018, at 18:04, Tim Peters <tim.peters@gmail.com> wrote:
However, against "as" is that its current use in "with" statements does something quite different:
with f() as name:
does not bind the result of `f()` to `name`, but the result of `f().__enter__()`. Whether that "should be" fatal, I don't know, but it's at least annoying ;-)
Prior art: COBOL uses "GIVING", as in:
ADD x, y GIVING z
No need to re-invent the wheel ;)
-- Ned Deily nad@python.org -- []
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/ guido%40python.org
-- --Guido van Rossum (python.org/~guido)

On Mon, Apr 23, 2018 at 03:36:10PM -0700, Guido van Rossum wrote:
Using 'as' was debated extensively on python-ideas. I don't like it for several reasons: [...]
For what it is worth, I was one of the original proponents of the "as" syntax, but at this point I am no longer going to argue for it. I'm satisfied that using "as" has disadvantages, and that := is an acceptable alternative. While I haven't changed my mind that putting the expression first and the target second is better, I'm satisfied that it is not so much better that it is worth extending this enormous discussion even further. In other words, as far as I am concerned, I am happy to take the syntax as decided: "expr as name" is rejected and "name := expr" is acceptable. I'm also satisfied that *at least for now* we should stick to only allowing simple names. We can always loosen the restriction later and allow arbitrarily complex targets at a later date, but we can't easily change our mind and prohibit them. -- Steve

On 2018-04-23 15:36, Guido van Rossum wrote:
- the semantics are subtly different from all other uses of 'as' in Python; I'd like to reserve 'as' for "not a plain assignment"
It's conceptually similar to "import as", no? The keyword "as" is a fuzzy yet intuitive concept already. Also, these "binding expressions" are not exactly plain assignment, as it has been known. -Mike

On Wed, Apr 25, 2018 at 5:54 AM, Mike Miller <python-dev@mgmiller.net> wrote:
Also, these "binding expressions" are not exactly plain assignment, as it has been known.
Aside from the restriction to binding only to a simple name, and the fact that they're also an expression with the same value, how are they not the same as plain assignment? Any discrepancies should be considered bugs. ChrisA

On 04/24/2018 12:59 PM, Chris Angelico wrote:
On Wed, Apr 25, 2018 at 5:54 AM, Mike Miller wrote:
Also, these "binding expressions" are not exactly plain assignment, as it has been known.
Aside from the restriction to binding only to a simple name, and the fact that they're also an expression with the same value, how are they not the same as plain assignment? Any discrepancies should be considered bugs.
They only bind to simple names, and they can occur in expressions. ;) -- ~Ethan~

On 2018-04-24 12:59, Chris Angelico wrote:
Aside from the restriction to binding only to a simple name, and the fact that they're also an expression with the same value, how are they not the same as plain assignment? Any discrepancies should be considered bugs.
Slightly different in that they return the value, and are expressions not assignment statements. Now, I agree that's not a difference of much consequence, but the difference from "import as" is not either. -Mike

On 25 April 2018 at 06:23, Mike Miller <python-dev@mgmiller.net> wrote:
Now, I agree that's not a difference of much consequence, but the difference from "import as" is not either.
Since this example has been brought up by a few folks now, I'll note that "import x.y.z as c" has never been entirely equivalent to a simple assignment, since it *also* avoids the default binding of "x" in the current namespace. These days, it's diverged even further, since we added a fallback to looking for the full "x.y.z" key in sys.modules if any part of the submodule lookup chain gets an AttributeError. The proposed name binding expressions aren't like that - they really are just an ordinary binding of the result of the given expression to the given name. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 04/23/2018 06:04 PM, Tim Peters wrote:
However, against "as" is that its current use in "with" statements does something quite different:
with f() as name:
does not bind the result of `f()` to `name`, but the result of `f().__enter__()`. Whether that "should be" fatal, I don't know, but it's at least annoying ;-)
This could be read a different way, though, since `with f()` calls `f().__enter__()`, you could read it as `(with f()) as name:`, in which case it does the same thing as an `as`-based binding expression would. Viewing it that way *also* helps alleviate the cognitive problem that `with f() as name` and `with (f() as name)` do two different things - the parentheses there are changing the precedence in the same way that `2 + 4 * 3` and `(2 + 4) * 3` do two different things. This sorta also works for `except`, if you read it as `(except SomeException) as e:`, but I think this fiction falls apart when you look at `import`, since `import foo` *already* binds "foo" to a name, and `import foo as bar` not only binds `foo` to `bar`, but also *doesn't* bind `foo` to `foo`.

On 4/23/2018 1:01 PM, Ethan Furman wrote:
On 04/22/2018 10:44 PM, Tim Peters wrote:
[Guido]
In reality there often are other conditions being applied to the match for which `if expr as name` is inadequate. The simplest would be something like
if ...: <something> elif (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2): <whatever>
And the match() call may not even be the first thing to check -- e.g. we could have
elif line is not None and (m := re.match('(.*):(.*)', line)) and m.group(1) == m.group(2):
I find myself warming more to binding expressions the more I keep them in mind while writing new code. And I think it may be helpful to continue showing real examples where they would help.
Today's example: I happened to code this a few hours ago:
diff = x - x_base if diff: g = gcd(diff, n) if g > 1: return g
It's not really hard to follow, but two levels of nesting "feels excessive", as does using the names "diff" and "g" three times each. It's _really_ an "and" test: if the diff isn't 0 and gcd(diff, n) > 1, return the gcd. That's how I _thought_ of it from the start.
Which this alternative expresses directly:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
So I really like being able to make the assignment in the expression, but I have a really hard time parsing it with the name first.
Attempting to read just the names first:
if diff scan for ending right paren found and g scan for ending right paren oops, found opening left paren scan for ending right paren found resume scanning for final right paren found > 1: return g
Attempting to read expressions first:
if x - x_base and gcd(diff, n) what's diff? scan backwards diff is x - x_base > 1: return g what's g? scan up and backwards g is gcd(diff, n)
Attempting to read interleaved:
if skip diff x - x_base back to diff as diff and skip g gcd(diff, n) back to g as g > 1: return g
On the other hand, if it were using the "as" keyword:
if (x - xbase as diff) and (gcd(diff, n) as g) > 1: return g
I would parse as:
if x - x_base as diff and gcd(diff, n) as g > 1: return g
For me at least, the last is much more readable. Thinking about it some more, the problem (or maybe just my problem) is that I see an "if" or "while" and the I look for the thing that is True or False, and using the ":=" syntax the first thing I see is a placeholder for a result that doesn't exist yet, making me constantly scan backwards and forwards to put all the pieces in the correct place.
With "as", it just flows forwards.
You need to borrow the time machine, and get with those early mathematicians that first said: Let x be the sum of y and z and convince them that what they should have said was: Let the sum of y and z be called x. :)

Tim Peters wrote:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
My problem with this is -- how do you read such code out loud? From my Pascal days I'm used to reading ":=" as "becomes". So this says: "If diff becomes x - base and g becomes gcd(diff, n) is greater than or equal to 1 then return g." But "diff becomes x - base" is not what we're testing! That makes it sound like the result of x - base may or may not get assigned to diff, which is not what's happening at all. The "as" variant makes more sense when you read it as an English sentence: if ((x - x_base) as diff) and ... "If x - x_base (and by the way, I'm going to call that diff so I can refer to it later) is not zero ..." -- Greg

[Tim]
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
[Greg Ewing <greg.ewing@canterbury.ac.nz>]
My problem with this is -- how do you read such code out loud?
In the message in which I first gave that example: if the diff isn't 0 and gcd(diff, n) > 1, return the gcd. That's how I _thought_ of it from the start. In my mind, `x - x_base` doesn't even exist except as a low-level definition of what "diff" means. It's different for the other test: _there_ `g` doesn't exist except as a shorthand for "the gcd". In one case it's the name that's important to me, and in the other case the expression. The entire function from which this came is doing all arithmetic modulo `n`, so `n` isn't in my mind either - it's a ubiquitous part of the background in this specific function. But you did ask how_I_ would read that code ;-) Anyone else is free to read it however they like. I naturally read it in the way that makes most sense to me in its context.
From my Pascal days I'm used to reading ":=" as "becomes". So this says:
"If diff becomes x - base and g becomes gcd(diff, n) is greater than or equal to 1 then return g."
But "diff becomes x - base" is not what we're testing!
I don't really follow that. In Python, if f() and g > 1: first tests whether `f()` "is truthy", regardless of whether it does or doesn't appear in a binding expression. Because this code is working with integers, there's an _implied_ "!= 0" comparison.
That makes it sound like the result of x - base may or may not get assigned to diff, which is not what's happening at all.
Then I suggest the problem you're having doesn't stem from the binding expression, but from that you're omitting to fill in the != 0 part: if you're not thrown by "greater than 1", I can't see how you can be thrown by "not zero".
The "as" variant makes more sense when you read it as an English sentence:
if ((x - x_base) as diff) and ...
"If x - x_base (and by the way, I'm going to call that diff so I can refer to it later) is not zero ..."
So read the original as "if diff (which is x - x_base) is not zero ...". Regardless, Guido has already said "as" is DOA (Dead On Arrival) (illustrating that it's also common enough in English to give a short name before its long-winded meaning ;-) ).

Tim Peters writes:
Regardless, Guido has already said "as" is DOA (Dead On Arrival) (illustrating that it's also common enough in English to give a short name before its long-winded meaning ;-) ).
The parens above are a gloss on a well-known acronym for those who have not encountered it before. Neologisms are usually written in the other order: "dead on arrival (DOA, for short)." ;-)

Stephen J. Turnbull wrote:
Neologisms are usually written in the other order: "dead on arrival (DOA, for short)." ;-)
Maybe we can make use of that? if (x - x_base) (diff) and gcd(diff, n) (g) > 1: That doesn't work, because the (...) look like function calls. But what if we used a different set of bracketing characters: if (x - x_base) {diff} and gcd(diff, n) {g} > 1: I think that's unambiguous, because you can't currently put {...} straight after an expression. To make it look even more like a neologism definition, we could require the bound names to be all-uppercase. :-) if (x - x_base) {DIFF} and gcd(DIFF, n) {G} > 1: return G -- Greg

On Tue, Apr 24, 2018 at 5:23 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Stephen J. Turnbull wrote:
Neologisms are usually written in the other order: "dead on arrival (DOA, for short)." ;-)
Maybe we can make use of that?
if (x - x_base) (diff) and gcd(diff, n) (g) > 1:
That doesn't work, because the (...) look like function calls. But what if we used a different set of bracketing characters:
if (x - x_base) {diff} and gcd(diff, n) {g} > 1:
I think that's unambiguous, because you can't currently put {...} straight after an expression.
To make it look even more like a neologism definition, we could require the bound names to be all-uppercase. :-)
if (x - x_base) {DIFF} and gcd(DIFF, n) {G} > 1: return G
Great! And to further enhance the neologism parallel, we could allow them to be defined at the bottom of the program. if {DIFF} and {G} > 1: return G with glossary: DIFF: x - x_base G: gcd(DIFF, n) Definite improvement over all the current proposals!! ChrisA

[Stephen J. Turnbull[
Neologisms are usually written in the other order: "dead on arrival (DOA, for short)." ;-)
[Greg Ewing <greg.ewing@canterbury.ac.nz>]
Maybe we can make use of that?
if (x - x_base) (diff) and gcd(diff, n) (g) > 1:
That doesn't work, because the (...) look like function calls. But what if we used a different set of bracketing characters:
if (x - x_base) {diff} and gcd(diff, n) {g} > 1:
I think that's unambiguous, because you can't currently put {...} straight after an expression.
As Guido noted more than once when this was still on python-ideas, this isn't a "a puzzle" to be solved by any technical tricks conceivable. He's not going to accept anything in his language that isn't at least plausibly evident. There's a long & distinguished history of other languages using ":=" for binding, which is why that one gained traction before this moved to python-dev.
To make it look even more like a neologism definition, we could require the bound names to be all-uppercase. :-)
if (x - x_base) {DIFF} and gcd(DIFF, n) {G} > 1: return G
Yes - now you're on the right track ;-)

I do think the pronunciation issue that Greg notices is important. I teach Python for most of my living, and reading and discussing code segments is an important part of that. When focussing on how Python actually *spells* something, you can't always jump to the higher-level meaning of a construct. For some complex expression—whether or not "binding expressions" are added—sometimes it makes sense to give a characterization of the *meaning* of the expression, but other times you want to say aloud the entire spelling of the expression. Although feelings are mixed about this, I like the "dunder" contraction for this purpose. It's less of a mouthful to say "dunder-init" than "underscore-underscore-init-underscore-underscore" aloud. And once you learn that shorthand, it's unambiguous. I think I'd pronounce: if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g As: "If diff bound to x minus x_base (is non-zero), and g bound to gcd of diff comma n is greater than 1, return g" But having a convention for pronouncing this would be nice, rather than it being my idiosyncrasy. On Mon, Apr 23, 2018 at 8:23 PM, Tim Peters <tim.peters@gmail.com> wrote:
[Tim]
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
[Greg Ewing <greg.ewing@canterbury.ac.nz>]
My problem with this is -- how do you read such code out loud?
In the message in which I first gave that example:
if the diff isn't 0 and gcd(diff, n) > 1, return the gcd. That's how I _thought_ of it from the start.
In my mind, `x - x_base` doesn't even exist except as a low-level definition of what "diff" means. It's different for the other test: _there_ `g` doesn't exist except as a shorthand for "the gcd". In one case it's the name that's important to me, and in the other case the expression. The entire function from which this came is doing all arithmetic modulo `n`, so `n` isn't in my mind either - it's a ubiquitous part of the background in this specific function.
But you did ask how_I_ would read that code ;-) Anyone else is free to read it however they like. I naturally read it in the way that makes most sense to me in its context.
From my Pascal days I'm used to reading ":=" as "becomes". So this says:
"If diff becomes x - base and g becomes gcd(diff, n) is greater than or equal to 1 then return g."
But "diff becomes x - base" is not what we're testing!
I don't really follow that. In Python,
if f() and g > 1:
first tests whether `f()` "is truthy", regardless of whether it does or doesn't appear in a binding expression. Because this code is working with integers, there's an _implied_ "!= 0" comparison.
That makes it sound like the result of x - base may or may not get assigned to diff, which is not what's happening at all.
Then I suggest the problem you're having doesn't stem from the binding expression, but from that you're omitting to fill in the != 0 part: if you're not thrown by "greater than 1", I can't see how you can be thrown by "not zero".
The "as" variant makes more sense when you read it as an English sentence:
if ((x - x_base) as diff) and ...
"If x - x_base (and by the way, I'm going to call that diff so I can refer to it later) is not zero ..."
So read the original as "if diff (which is x - x_base) is not zero ...".
Regardless, Guido has already said "as" is DOA (Dead On Arrival) (illustrating that it's also common enough in English to give a short name before its long-winded meaning ;-) ). _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/ mertz%40gnosis.cx
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

On 24 April 2018 at 22:30, David Mertz <mertz@gnosis.cx> wrote:
I do think the pronunciation issue that Greg notices is important. I teach Python for most of my living, and reading and discussing code segments is an important part of that. When focussing on how Python actually *spells* something, you can't always jump to the higher-level meaning of a construct. For some complex expression—whether or not "binding expressions" are added—sometimes it makes sense to give a characterization of the *meaning* of the expression, but other times you want to say aloud the entire spelling of the expression.
Although feelings are mixed about this, I like the "dunder" contraction for this purpose. It's less of a mouthful to say "dunder-init" than "underscore-underscore-init-underscore-underscore" aloud. And once you learn that shorthand, it's unambiguous.
I think I'd pronounce:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
As:
"If diff bound to x minus x_base (is non-zero), and g bound to gcd of diff comma n is greater than 1, return g"
Pronouncing it as "name bound to expr" would also fit well with calling the overall construct a binding expression. You could also postpone the definitions to the end when speaking aloud: "if diff is true and g is greater than 1, then return g, given that diff is bound to ex minus ex-base and g is bound to the gcd of diff and n" However, that long form sounded awkward to me, though, so I ended up wanting to rephrase it as just: "if diff is true and g is greater than 1, then return g, given that diff is ex minus ex-base and g is the gcd of diff and n" (The only change is to replace both occurrences of "is bound to" with a simple "is") And writing that out actually gave me an idea that I don't believe has come up before (or if it did, it got lost somewhere in the depths of a long python-ideas thread): if (diff is= x - x_base) and (g is= gcd(diff, n)) > 1: return g With the mnemonic for what the binding expression means being the following invariant: _rhs = expr assert (name is= _rhs) is _rhs and name is _rhs In a very real sense, that's *exactly* recreating the C pointer semantics for "==" ("check if two pointers reference the same object") vs "=" ("make two pointers reference the same object"), we'd just be spelling it as "is" vs "is=". Given that spelling, a reasonable inline pronunciation of "is=" would still be Davids suggestion of "is bound to": "if diff is bound to ex minux ex-base and is true and g is bound to the gcd of diff and n and is greater than 1, then return g" Simplifying "is bound to" to "is" in the post-definition form would just be a verbal shorthand. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Mon, Apr 23, 2018 at 4:54 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Tim Peters wrote:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
My problem with this is -- how do you read such code out loud?
It could be-- "if diff, which we let equal x - x_base, and g, which ..." or "if diff, which we set equal to x - x_base, and g, which ...." or "if diff, which we define to be x - x_base, and g, which ...." or "if diff, which we define as x - x_base, and g, which ....." etc. --Chris
From my Pascal days I'm used to reading ":=" as "becomes". So this says:
"If diff becomes x - base and g becomes gcd(diff, n) is greater than or equal to 1 then return g."
But "diff becomes x - base" is not what we're testing! That makes it sound like the result of x - base may or may not get assigned to diff, which is not what's happening at all.
The "as" variant makes more sense when you read it as an English sentence:
if ((x - x_base) as diff) and ...
"If x - x_base (and by the way, I'm going to call that diff so I can refer to it later) is not zero ..."
-- Greg
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/chris.jerdonek%40gmail.co...

Chris Jerdonek wrote:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
"if diff, which we let equal x - x_base, and g, which ..." or "if diff, which we set equal to x - x_base, and g, which ...." or "if diff, which we define to be x - x_base, and g, which ...." or "if diff, which we define as x - x_base, and g, which ....." etc.
How about "being" as a keyword: if (diff being x - x_base) and (g being gcd(diff, n)) > 1: return g An advantage is that you're not likely to be tempted to write diff being x - x_base on its own as a statement. -- Greg

On Tue, Apr 24, 2018 at 5:12 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Chris Jerdonek wrote:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
"if diff, which we let equal x - x_base, and g, which ..." or "if diff, which we set equal to x - x_base, and g, which ...." or "if diff, which we define to be x - x_base, and g, which ...." or "if diff, which we define as x - x_base, and g, which ....." etc.
How about "being" as a keyword:
if (diff being x - x_base) and (g being gcd(diff, n)) > 1: return g
An advantage is that you're not likely to be tempted to write
diff being x - x_base
on its own as a statement.
Considering that we have ':=', 'as', and 'from', I very much doubt that *any* proposal requiring a new keyword is going to fly. The bar for creating new keywords is a lot higher than that. We hashed out a lot of this on python-ideas; it's almost certainly a waste of time to go through it all again now. I have no intention of editing the PEP to recommend a brand new keyword. ChrisA

On 24 April 2018 at 08:12, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Chris Jerdonek wrote:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
"if diff, which we let equal x - x_base, and g, which ..." or "if diff, which we set equal to x - x_base, and g, which ...." or "if diff, which we define to be x - x_base, and g, which ...." or "if diff, which we define as x - x_base, and g, which ....." etc.
How about "being" as a keyword:
if (diff being x - x_base) and (g being gcd(diff, n)) > 1: return g
An advantage is that you're not likely to be tempted to write
diff being x - x_base
on its own as a statement.
I like this term, but I don't like having more reserved words. Or PEP can make it an official way to read := (z := x + y) is _called_ a binding expression. (z := x + y) _reads_ as "z being x + y" -- Ivan

On 24/04/2018 02:42, Chris Jerdonek wrote:
On Mon, Apr 23, 2018 at 4:54 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Tim Peters wrote:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g My problem with this is -- how do you read such code out loud? It could be...
"if diff, which we define as x - x_base, and g, which ....." etc.
That's good. It also makes it natural to expect only a simple name. One can "define" a name, but assignment to a complex left-side expression is not definition (binding). Jeff Allen

On 04/23/2018 06:42 PM, Chris Jerdonek wrote:
On Mon, Apr 23, 2018 at 4:54 PM, Greg Ewing wrote:
Tim Peters wrote:
if (diff := x - x_base) and (g := gcd(diff, n)) > 1: return g
My problem with this is -- how do you read such code out loud?
It could be--
"if diff, which we let equal x - x_base, and g, which ..." or "if diff, which we set equal to x - x_base, and g, which ...." or "if diff, which we define to be x - x_base, and g, which ...." or "if diff, which we define as x - x_base, and g, which ....." etc.
Thanks, Chris J. For myself, I can read that as "if diff, which is x - x_base, and g, which is ..." That works for me. Changing my vote to +1 (but only for simple name bindings) -- ~Ethan~
participants (21)
-
Antoine Pitrou
-
Barry Warsaw
-
Chris Angelico
-
Chris Jerdonek
-
David Mertz
-
Ethan Furman
-
Glenn Linderman
-
Greg Ewing
-
Guido van Rossum
-
Ivan Levkivskyi
-
Jeff Allen
-
Matěj Cepl
-
Mike Miller
-
Ned Deily
-
Nick Coghlan
-
Paul G
-
Stephen J. Turnbull
-
Steve Holden
-
Steven D'Aprano
-
Sven R. Kunze
-
Tim Peters