PEP 572 version 2: Statement-Local Name Bindings

After dozens of posts and a wide variety of useful opinions and concerns being raised, here is the newest version of PEP 572 for your debating pleasure. Formatted version: https://www.python.org/dev/peps/pep-0572/ There are now several more examples, greater clarity in edge cases, and improved wording of the actual proposal and specifications. Also, the reference implementation has been significantly enhanced, for those who wish to try this themselves. ChrisA PEP: 572 Title: Syntax for Statement-Local Name Bindings Author: Chris Angelico <rosuav@gmail.com> Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 28-Feb-2018 Python-Version: 3.8 Post-History: 28-Feb-2018, 02-Mar-2018 Abstract ======== Programming is all about reusing code rather than duplicating it. When an expression needs to be used twice in quick succession but never again, it is convenient to assign it to a temporary name with small scope. By permitting name bindings to exist within a single statement only, we make this both convenient and safe against name collisions. Rationale ========= When a subexpression is used multiple times in a list comprehension, there are currently several ways to spell this, none of which is universally accepted as ideal. A statement-local name allows any subexpression to be temporarily captured and then used multiple times. Additionally, this syntax can in places be used to remove the need to write an infinite loop with a ``break`` in it. Capturing part of a ``while`` loop's condition can improve the clarity of the loop header while still making the actual value available within the loop body. Syntax and semantics ==================== In any context where arbitrary Python expressions can be used, a named expression can appear. This must be parenthesized for clarity, and is of the form ``(expr as NAME)`` where ``expr`` is any valid Python expression, and ``NAME`` is a simple name. The value of such a named expression is the same as the incorporated expression, with the additional side-effect that NAME is bound to that value in all retrievals for the remainder of the current statement. Just as function-local names shadow global names for the scope of the function, statement-local names shadow other names for that statement. They can also shadow each other, though actually doing this should be strongly discouraged in style guides. Assignment to statement-local names is ONLY through this syntax. Regular assignment to the same name will remove the statement-local name and affect the name in the surrounding scope (function, class, or module). Statement-local names never appear in locals() or globals(), and cannot be closed over by nested functions. Execution order and its consequences ------------------------------------ Since the statement-local name binding lasts from its point of execution to the end of the current statement, this can potentially cause confusion when the actual order of execution does not match the programmer's expectations. Some examples:: # A simple statement ends at the newline or semicolon. a = (1 as y) print(y) # NameError # The assignment ignores the SLNB - this adds one to 'a' a = (a + 1 as a) # Compound statements usually enclose everything... if (re.match(...) as m): print(m.groups(0)) print(m) # NameError # ... except when function bodies are involved... if (input("> ") as cmd): def run_cmd(): print("Running command", cmd) # NameError # ... but function *headers* are executed immediately if (input("> ") as cmd): def run_cmd(cmd=cmd): # Capture the value in the default arg print("Running command", cmd) # Works Some of these examples should be considered *bad code* and rejected by code review and/or linters; they are not, however, illegal. Example usage ============= These list comprehensions are all approximately equivalent:: # Calling the function twice stuff = [[f(x), x/f(x)] for x in range(5)] # External helper function def pair(x, value): return [value, x/value] stuff = [pair(x, f(x)) for x in range(5)] # Inline helper function stuff = [(lambda y: [y,x/y])(f(x)) for x in range(5)] # Extra 'for' loop - see also Serhiy's optimization stuff = [[y, x/y] for x in range(5) for y in [f(x)]] # Iterating over a genexp stuff = [[y, x/y] for x, y in ((x, f(x)) for x in range(5))] # Expanding the comprehension into a loop stuff = [] for x in range(5): y = f(x) stuff.append([y, x/y]) # Wrapping the loop in a generator function def g(): for x in range(5): y = f(x) yield [y, x/y] stuff = list(g) # Using a statement-local name stuff = [[(f(x) as y), x/y] for x in range(5)] If calling ``f(x)`` is expensive or has side effects, the clean operation of the list comprehension gets muddled. Using a short-duration name binding retains the simplicity; while the extra ``for`` loop does achieve this, it does so at the cost of dividing the expression visually, putting the named part at the end of the comprehension instead of the beginning. Statement-local name bindings can be used in any context, but should be avoided where regular assignment can be used, just as ``lambda`` should be avoided when ``def`` is an option. As the name's scope extends to the full current statement, even a block statement, this can be used to good effect in the header of an ``if`` or ``while`` statement:: # Current Python, not caring about function return value while input("> ") != "quit": print("You entered a command.") # Current Python, capturing return value - four-line loop header while True: command = input("> "); if command == "quit": break print("You entered:", command) # Proposed alternative to the above while (input("> ") as command) != "quit": print("You entered:", command) # See, for instance, Lib/pydoc.py if (re.search(pat, text) as match): print("Found:", match.group(0)) while (sock.read() as data): print("Received data:", data) Particularly with the ``while`` loop, this can remove the need to have an infinite loop, an assignment, and a condition. It also creates a smooth parallel between a loop which simply uses a function call as its condition, and one which uses that as its condition but also uses the actual value. Performance costs ================= The cost of SLNBs must be kept to a minimum, particularly when they are not used; the normal case MUST NOT be measurably penalized. SLNBs are expected to be uncommon, and using many of them in a single function should definitely be discouraged. Thus the current implementation uses a linked list of SLNB cells, with the absence of such a list being the normal case. This list is used for code compilation only; once a function's bytecode has been baked in, execution of that bytecode has no performance cost compared to regular assignment. Other Python implementations may choose to do things differently, but a zero run-time cost is strongly recommended, as is a minimal compile-time cost in the case where no SLNBs are used. Open questions ============== 1. What happens if the name has already been used? ``(x, (1 as x), x)`` Currently, prior usage functions as if the named expression did not exist (following the usual lookup rules); the new name binding will shadow the other name from the point where it is evaluated until the end of the statement. Is this acceptable? Should it raise a syntax error or warning? 2. Syntactic confusion in ``except`` statements. While technically unambiguous, it is potentially confusing to humans. In Python 3.7, parenthesizing ``except (Exception as e):`` is illegal, and there is no reason to capture the exception type (as opposed to the exception instance, as is done by the regular syntax). Should this be made outright illegal, to prevent confusion? Can it be left to linters? It may also (and independently) be of value to use a subscope for the normal except clause binding, such that ``except Exception as e:`` will no longer unbind a previous use of the name ``e``. 3. Similar confusion in ``with`` statements, with the difference that there is good reason to capture the result of an expression, and it is also very common for ``__enter__`` methods to return ``self``. In many cases, ``with expr as name:`` will do the same thing as ``with (expr as name):``, adding to the confusion. 4. Should closures be able to refer to statement-local names? Either way, there will be edge cases that make no sense. Assigning to a name will "push through" the SLNB and bind to the regular name; this means that a statement ``x = x`` will promote the SLNB to full name, and thus has an impact. Closing over statement-local names, however, introduces scope and lifetime confusions, as it then becomes possible to have two functions in almost the same context, closing over the same name, referring to two different cells. Alternative proposals ===================== Proposals of this nature have come up frequently on python-ideas. Below are a number of alternative syntaxes, some of them specific to comprehensions, which have been rejected in favour of the one given above. 1. ``where``, ``let``, ``given``:: stuff = [(y, x/y) where y = f(x) for x in range(5)] stuff = [(y, x/y) let y = f(x) for x in range(5)] stuff = [(y, x/y) given y = f(x) for x in range(5)] This brings the subexpression to a location in between the 'for' loop and the expression. It introduces an additional language keyword, which creates conflicts. Of the three, ``where`` reads the most cleanly, but also has the greatest potential for conflict (eg SQLAlchemy and numpy have ``where`` methods, as does ``tkinter.dnd.Icon`` in the standard library). 2. ``with``:: stuff = [(y, x/y) with y = f(x) for x in range(5)] As above, but reusing the `with` keyword. Doesn't read too badly, and needs no additional language keyword. Is restricted to comprehensions, though, and cannot as easily be transformed into "longhand" for-loop syntax. Has the C problem that an equals sign in an expression can now create a name binding, rather than performing a comparison. 3. ``with... as``:: stuff = [(y, x/y) with f(x) as y for x in range(5)] As per option 2, but using ``as`` in place of the equals sign. Aligns syntactically with other uses of ``as`` for name binding, but a simple transformation to for-loop longhand would create drastically different semantics; the meaning of ``with`` inside a comprehension would be completely different from the meaning as a stand-alone statement. 4. ``EXPR as NAME`` without parentheses:: stuff = [[f(x) as y, x/y] for x in range(5)] Omitting the parentheses from this PEP's proposed syntax introduces many syntactic ambiguities. 5. Adorning statement-local names with a leading dot:: stuff = [[(f(x) as .y), x/.y] for x in range(5)] This has the advantage that leaked usage can be readily detected, removing some forms of syntactic ambiguity. However, this would be the only place in Python where a variable's scope is encoded into its name, making refactoring harder. This syntax is quite viable, and could be promoted to become the current recommendation if its advantages are found to outweigh its cost. 6. Allowing ``(EXPR as NAME)`` to assign to any form of name. This is exactly the same as the promoted proposal, save that the name is bound in the same scope that it would otherwise have. Any expression can assign to any name, just as it would if the ``=`` operator had been used. Discrepancies in the current implementation =========================================== 1. SLNBs are implemented using a special (and mostly-invisible) name mangling. They may sometimes appear in globals() and/or locals() with their simple or mangled names (but buggily and unreliably). They should be suppressed as though they were guinea pigs. References ========== .. [1] Proof of concept / reference implementation (https://github.com/Rosuav/cpython/tree/statement-local-variables) Copyright ========= This document has been placed in the public domain. .. Local Variables: mode: indented-text indent-tabs-mode: nil sentence-end-double-space: t fill-column: 70 coding: utf-8 End:

On 2 March 2018 at 11:43, Chris Angelico <rosuav@gmail.com> wrote:
This is a distinct improvement - thanks for incorporating the varied feedback so well. I still remain -1, though. Reasons scattered through the other thread but mostly relating to the fact that there are subtle differences between statement-local names and other names in Python (notably that you can't close over them). I don't think the benefits of the proposal are sufficient to justify introducing a new similar-but-subtly-different type of name. Paul

On Fri, Mar 2, 2018 at 10:56 PM, Paul Moore <p.f.moore@gmail.com> wrote:
That's fine :) I'm under no illusions that everyone will adore this proposal (hey, I'm no more than +0.5 on it myself). The greatest goal of this PEP is to record the arguments for and against each of the viable alternatives, as a long-term document. ChrisA

On 02/03/18 11:43, Chris Angelico wrote:
I haven't said this yet, so thanks Chris for putting this all together. Even if the result is a rejected PEP, at least we have everything in one place. [snip]
This (and the equivalent in while loops) is the big win in the PEP, in my opinion. The number of ugly loops I've had to write in Python because I can't write "while (something_to_do() as event):"... +1 on this.
# Using a statement-local name stuff = [[(f(x) as y), x/y] for x in range(5)]
As Paul said, the asymmetry of this bothers me a lot. It doesn't read naturally to me. -1 on this.
I wouldn't worry too much about this case. Anyone gratuitously reusing names like that deserves all that will be coming to them.
Honestly I prefer this syntax for comprehensions. It doesn't read perfectly but it's good enough (though I am a mathematician by original training, so set notation works for me anyway), and the syntax is clear and limited. I'm not sure the case for fully general statement-local variables has been made. So, counter-proposal(s): 1. Allow "(f() as a)" in the conditions of "if" and "while" statements, after some arguing as to whether "a" is a special snowflake or just a normal local variable. 2. Add a "with" clause to comprehensions to make comprehension-local variables (presumably the same class of thing as the iteration variables). -- Rhodri James *-* Kynesim Ltd

+1 on extracting the big win for "if" and "while" (the regex case is wonderul). It would be see as an "extended if/while" rather than a general statement assignation. +1 on list comprehensions, even if I prefer the [(y, x/y) with y = f(x) for x in range(5)] or [(y, x/y) for x in range(5) with y = f(x)] syntax (more close to "for y in [ f(x) ]". Le 2 mars 2018 15:54, "Rhodri James" <rhodri@kynesim.co.uk> a écrit : On 02/03/18 11:43, Chris Angelico wrote:
I haven't said this yet, so thanks Chris for putting this all together. Even if the result is a rejected PEP, at least we have everything in one place. [snip] # Compound statements usually enclose everything...
This (and the equivalent in while loops) is the big win in the PEP, in my opinion. The number of ugly loops I've had to write in Python because I can't write "while (something_to_do() as event):"... +1 on this. # Using a statement-local name
stuff = [[(f(x) as y), x/y] for x in range(5)]
As Paul said, the asymmetry of this bothers me a lot. It doesn't read naturally to me. -1 on this. 1. What happens if the name has already been used? ``(x, (1 as x), x)``
I wouldn't worry too much about this case. Anyone gratuitously reusing names like that deserves all that will be coming to them. Alternative proposals
Honestly I prefer this syntax for comprehensions. It doesn't read perfectly but it's good enough (though I am a mathematician by original training, so set notation works for me anyway), and the syntax is clear and limited. I'm not sure the case for fully general statement-local variables has been made. So, counter-proposal(s): 1. Allow "(f() as a)" in the conditions of "if" and "while" statements, after some arguing as to whether "a" is a special snowflake or just a normal local variable. 2. Add a "with" clause to comprehensions to make comprehension-local variables (presumably the same class of thing as the iteration variables). -- Rhodri James *-* Kynesim Ltd _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

2018-03-02 7:03 GMT-08:00 Robert Vanden Eynde <robertve92@gmail.com>:
I wonder if we could have a more limited change to the language that would allow only the as/while use cases. Specifically, that means we could do: while do_something() as x: print(x) if re.match(r'.*', text) as match: print(match.groups()) Parentheses would no longer be necessary for syntactic ambiguity, and there is no real need for new scoping rules—we could just create new locals. This would alleviate some of the problems with the current proposal, such as complicated scoping rules and ugly syntax in comprehensions.

On Sat, Mar 3, 2018 at 5:27 AM, Jelle Zijlstra <jelle.zijlstra@gmail.com> wrote:
The trouble with this is that it's extremely restricted. It works for the regex case, because re.match() has been specifically written to function as either "return a match object" or "return true/false to indicate a match", by guaranteeing to return a truthy object or None, and nothing else. It would NOT work for anything where the bool() of the desired object doesn't exactly match the loop's condition. For instance, if do_something() returns None when it's done, but might return an empty string during operation, the correct condition is "while do_something() is not None" - and you can't capture the whole condition any more. To be able to write "while (do_something() as x) is not None:", you have to have the more general syntax that's used in this PEP. ChrisA

On 02/03/18 18:27, Jelle Zijlstra wrote:
2018-03-02 7:03 GMT-08:00 Robert Vanden Eynde <robertve92@gmail.com>:
Guys, please don't email to me *and* the mailing list. Getting two copies of your deathless prose makes me less likely to pay attention to you, not more. -- Rhodri James *-* Kynesim Ltd

@Rhodri, this is what Everybody does because you hit the "reply to all" button, but, we don't receive two copies on the mail, I don't know why (but that's a good thing). Le 2 mars 2018 20:48, "Rhodri James" <rhodri@kynesim.co.uk> a écrit : On 02/03/18 18:27, Jelle Zijlstra wrote:
2018-03-02 7:03 GMT-08:00 Robert Vanden Eynde <robertve92@gmail.com>:
Guys, please don't email to me *and* the mailing list. Getting two copies of your deathless prose makes me less likely to pay attention to you, not more. -- Rhodri James *-* Kynesim Ltd

On Sat, Mar 3, 2018 at 6:55 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
If you're using a poor-quality mail client, you may have to fix things manually. (Or get a better mail client - it's up to you.) If "Reply-All" sends to both the sender and the list, either look for a "Reply-List" button, or manually delete the sender's address and post only to the list. And please, don't top-post. Again, if your mail client encourages top posting, either override it, or get a better one. ChrisA

Le 2 mars 2018 21:02, "Chris Angelico" <rosuav@gmail.com> a écrit : On Sat, Mar 3, 2018 at 6:55 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
If you're using a poor-quality mail client, you may have to fix things manually. (Or get a better mail client - it's up to you.) If "Reply-All" sends to both the sender and the list, either look for a "Reply-List" button, or manually delete the sender's address and post only to the list. And please, don't top-post. Again, if your mail client encourages top posting, either override it, or get a better one. ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ @Chris @Rohdri (@Jonathan below) For morons like me who didn't know what "top-posting" was, I went on Wikipedia (https://en.m.wikipedia.org/wiki/Posting_style). I think newcomers to mailing list aren't supposed to know all those "de facto standard" (they never used mailing list before ? And I'm 25 years old, not 12), so please don't tell "our mail client is bad, get a new one", I'm using gmail on Android and gmail on Google.com, that's like, very standard. But if you have other mail clients to suggest me, to configure my user interface, feel free to help me, but they will always be people not knowing these good mailing practice (is using html mail a bad practice too ? It used to be). That's why moving to MM3 so that newcomers can use tool a bit more familiar like a forum and will not make the same "mistakes" (top posting, reply only to sender...) over and over again is a good thing. As someone said in their mail comparing web-based to mail, mailing needs configuration. @Jonathan, thanks for the settings, I didn't know about it, it solves this common problem !

On 03/02/2018 12:47 PM, Robert Vanden Eynde wrote:
You looked it up and researched it, so definitely NOT a moron. ;)
Standard, maybe. Good for basic email discussion, nevermind mailing lists? Nope (My humble opinion, of course.)
But if you have other mail clients to suggest me, to configure my user interface, feel free to help me, but they will always be people not knowing these good mailing practice (is using html mail a bad practice too ? It used to be).
The Python mailing lists are plain-text, so html-mail is not great.
Most things worth using require configuration/customization (cars, kitchens, and definitely software). Thanks for persevering! -- ~Ethan~

Le 2 mars 2018 22:03, "Ethan Furman" <ethan@stoneleaf.us> a écrit : On 03/02/2018 12:47 PM, Robert Vanden Eynde wrote: @Chris @Rohdri (@Jonathan below)
You looked it up and researched it, so definitely NOT a moron. ;) I think newcomers to mailing list aren't supposed to know all those "de
Standard, maybe. Good for basic email discussion, nevermind mailing lists? Nope (My humble opinion, of course.) But if you have other mail clients to suggest me, to configure my user
The Python mailing lists are plain-text, so html-mail is not great. That's why moving to MM3 so that newcomers can use tool a bit more familiar
Most things worth using require configuration/customization (cars, kitchens, and definitely software). Thanks for persevering! -- ~Ethan~ _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ Configuration is awesome but having good default helps you not having to tell everyone "configure that, the default is bad" (the "no send duplicate" settings maybe changed default or you misclicked on it). I'm just saying, be comprehensive to newcomer making mistakes, or give them a good man to read when they sign in :)

On Sat, Mar 3, 2018 at 7:47 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
As Ethan says, not a moron. (Though the way he put it, he sounded like Wheatley... and if you don't know what I'm talking about, check out Portal and Portal 2, they're games well worth playing.) One of the beauties of text is that it's easy to research; if someone drops a term like "top-posting", you key that into your search engine, and voila, extra information. This is another advantage of separate standards and common conventions. Everything works with everything else, because it is simple and because nobody has to do everything themselves. How do you use git to manage your CPython source tree? It's exactly the same as using git for anything else, so you don't need Python-specific information, just git-specific information. How do you use a mailing list to discuss proposed language changes in Python? Again, it's generic stuff about mailing lists, so you don't need anything from Python. This is why we work with open standards. Email and mailing lists are an important part of this. ChrisA

Le 2 mars 2018 22:13, "Chris Angelico" <rosuav@gmail.com> a écrit : On Sat, Mar 3, 2018 at 7:47 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
As Ethan says, not a moron. (Though the way he put it, he sounded like Wheatley... and if you don't know what I'm talking about, check out Portal and Portal 2, they're games well worth playing.) One of the beauties of text is that it's easy to research; if someone drops a term like "top-posting", you key that into your search engine, and voila, extra information. This is another advantage of separate standards and common conventions. Everything works with everything else, because it is simple and because nobody has to do everything themselves. How do you use git to manage your CPython source tree? It's exactly the same as using git for anything else, so you don't need Python-specific information, just git-specific information. How do you use a mailing list to discuss proposed language changes in Python? Again, it's generic stuff about mailing lists, so you don't need anything from Python. This is why we work with open standards. Email and mailing lists are an important part of this. ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ As said on the Wikipedia page I shared, """ For a long time the traditional style was to post the answer below as much of the quoted original as was necessary to understand the reply (bottom or inline). Many years later, when email became widespread in business communication, it became a widespread practice to reply above the entire original and leave it (supposedly untouched) below the reply. While each online community <https://en.m.wikipedia.org/wiki/Virtual_community> differs on which styles are appropriate or acceptable, within some communities the use of the “wrong” method risks being seen as a breach of netiquette <https://en.m.wikipedia.org/wiki/Netiquette>, and can provoke vehement response from community regulars. """ De-facto I wouldn't know which convention (of course now I clearly prefer "interleaved posting" for obvious reason... Now that I know how to do it) so a Manual or HowTo when signing in would be an idea :) Of course, I clearly prefer interleaved posting for obvious reasons... now that I know how to produce it using my daily mail client.

Le 2 mars 2018 22:21, "Robert Vanden Eynde" <robertve92@gmail.com> a écrit : Le 2 mars 2018 22:13, "Chris Angelico" <rosuav@gmail.com> a écrit : On Sat, Mar 3, 2018 at 7:47 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
As Ethan says, not a moron. (Though the way he put it, he sounded like Wheatley... and if you don't know what I'm talking about, check out Portal and Portal 2, they're games well worth playing.) One of the beauties of text is that it's easy to research; if someone drops a term like "top-posting", you key that into your search engine, and voila, extra information. This is another advantage of separate standards and common conventions. Everything works with everything else, because it is simple and because nobody has to do everything themselves. How do you use git to manage your CPython source tree? It's exactly the same as using git for anything else, so you don't need Python-specific information, just git-specific information. How do you use a mailing list to discuss proposed language changes in Python? Again, it's generic stuff about mailing lists, so you don't need anything from Python. This is why we work with open standards. Email and mailing lists are an important part of this. ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ As said on the Wikipedia page I shared, """ For a long time the traditional style was to post the answer below as much of the quoted original as was necessary to understand the reply (bottom or inline). Many years later, when email became widespread in business communication, it became a widespread practice to reply above the entire original and leave it (supposedly untouched) below the reply. While each online community <https://en.m.wikipedia.org/wiki/Virtual_community> differs on which styles are appropriate or acceptable, within some communities the use of the “wrong” method risks being seen as a breach of netiquette <https://en.m.wikipedia.org/wiki/Netiquette>, and can provoke vehement response from community regulars. """ De-facto I wouldn't know which convention (of course now I clearly prefer "interleaved posting" for obvious reason... Now that I know how to do it) so a Manual or HowTo when signing in would be an idea :) Of course, I clearly prefer interleaved posting for obvious reasons... now that I know how to produce it using my daily mail client. Damn it, I forgot to erase the first paragraph, I'm used to forum app where you can quickly edit a sent message when nobody already read it. By the way, yes, text is wonderful, searchable and archivable, that's one of the reasons to love programming and markup language :)

On Fri, Mar 2, 2018 at 2:56 PM Robert Vanden Eynde <robertve92@gmail.com> wrote:
*de-lurks* There is an option in your personal settings for the list to choose whether to receive duplicate copies of messages sent to both you and the list. It is usually wise to set that to "no". To check this option, go to https://mail.python.org/mailman/options/python-ideas, enter your email address and password (if you don't recall your password, just enter your email address and click "Remind" at the bottom of the page), and click "Log in". From there, you can change a variety of options, including the one about receiving duplicate copies of messages aimed at both you and the list.

On Fri, Mar 2, 2018 at 10:27 AM, Jelle Zijlstra <jelle.zijlstra@gmail.com> wrote:
The "while" case is the only part of the PEP that has any traction with me. It doesn't add any keywords, scope can be identical to "with" and it cleans up a code pattern that is very common. Every one of these comprehension examples has me scratching my head, thinking back a couple hundred (thousand? :) ) posts to Barry's quip, "Sometimes a for loop is just better" (or something along those lines).

On Sat, Mar 3, 2018 at 7:04 AM, Eric Fahlgren <ericfahlgren@gmail.com> wrote:
How often do you have a loop like this where you actually want to capture the exact condition? I can think of two: regular expressions (match object or None), and socket read (returns empty string on EOF). This simplified form is ONLY of value in that sort of situation; as soon as you want to add a condition around it, this stops working (you can't say "while do_something() is not _sentinel as x:" because all you'll get is True). And if you are looking for one specific return value as your termination signal, you can write "for x in iter(do_something, None):".
True, but there's a code smell to an unrolled 'for' loop that could have been a comprehension had it not been for one trivial point of pedantry. So there are advantages and disadvantages to each. ChrisA

On 2018-03-02 12:20, Chris Angelico wrote:
But you could have "while (do_something() as x) is not _sentinel". Not sure how proponents and opponents would react to that. Limiting the SLNB to the beginning of block-level statements seems perverse in a way, but also might cut down on gratuitous overuse mixed into all kinds of weird positions in statements. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On Sat, Mar 3, 2018 at 7:31 AM, Brendan Barnwell <brenbarn@brenbarn.net> wrote:
If we're going to have something that's syntactically valid in an expression, I don't see a lot of value in requiring that it be inside a while or if header. That just leads to confusion and mess down the road. ChrisA

On Fri, Mar 2, 2018 at 12:20 PM, Chris Angelico <rosuav@gmail.com> wrote:
For me, it's all the time. Our geometry modeling database is hierarchical, so you see things like this all over kernel, often with a lot more code than just that one line calculating the cumulative scale factor:
which would turn into

On Sat, Mar 3, 2018 at 1:53 AM, Rhodri James <rhodri@kynesim.co.uk> wrote:
No problem. And I agree, a rejected PEP is still a successful result here. (Am I going to get a reputation for captaining dead PEPs?)
Interesting. I fully expected to get a lot more backlash for the if/while usage, but a number of people are saying that that's the only (or the biggest) part of this proposal that they like. ChrisA

I remain -1 on the PEP, but thank you very much Chris for the great work writing it and the extra clarifications in it. The part I like most about the proposal is the use in blocks, like: if (re.match(...) as m): print(m.groups(0)) if (input("> ") as cmd): def run_cmd(cmd=cmd): # Capture the value in the default arg print("Running command", cmd) # Works But what I like has nothing much to do with scope limitation. It is only about the idea of introducing a variable into a block. We can do that with a context manager. No, there's no cleanup of the namespace in a straightforward approach, but using e.g. `_x` as a convention for names meant to be transient is already established practice and requires no special syntax. So right now, I can do these: class bind(object): def __init__(self, *args): self.args = args def __enter__(self): return self.args[0] if len(self.args)==1 else self.args def __exit__(self, *args): pass
This would cover 98% of the cases that I would want with the proposed statement-local name bindings. I suppose I could write something much more obscure that poked into the call stack and actually deleted names from scopes in the context manager. But really I rarely care about the temporary use of a few names, especially if the same few temporary names are used repeatedly for these things. On Fri, Mar 2, 2018 at 8:04 AM, Chris Angelico <rosuav@gmail.com> wrote:
-- 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 Fri, Mar 2, 2018 at 10:44 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
The only difference from simple assignment is just visual and to be more self documenting. Basically, it just says (to me at least): "I intend to use these name within this block, but don't care about them elsewhere." It's sort of an informal scope without actual scoping rules. But of course, this is just existing Python, and anyone who wants to or doesn't is free to use or not use that style. In truth, I've thought about doing it from time to time, but never actually bothered in production code, just as a toy. -- 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 03/02/2018 08:04 AM, Chris Angelico wrote:
On Sat, Mar 3, 2018 at 1:53 AM, Rhodri James wrote:
On 02/03/18 11:43, Chris Angelico wrote:
This is the part of the PEP that I really like as well -- far more useful to me than the list-comp examples. I could even be +0.5 if the "if/while/for" compound statements were kept and the comprehensions dropped -- but not with leading dots -- I would rather do $ than . . Kind of like decorators, they shouldn't be used all that often. -- ~Ethan~

PEP 572 as it stands seems to have many problems: * Asymmetry between the first and subsequent uses of the bound value * Embedded as-clauses are subtle and hard to spot * Ever more convoluted semantics are being invented to address percieved pitfalls Alternative proposal -------------------- Getting back to the original motivating use case of intermediate values in comprehensions, to my mind there is really only one clear and obvious way to write such things: [(f(y), g(y)) for x in things where y = h(x)] Possible objections to this: * Requires a new keyword, which may break existing code. - Yes, but "where" is an unlikely choice of name, being neither a noun, verb or adjective, so it probably wouldn't break very *much* code. In return, we get something that resonates with language mathematicians have been using for centuries, and a word that can be usefully googled. * Out-of-order evaluation - Comprehensions and if-expressions already have that. Extension to other use cases ---------------------------- There are some other situations in which it could be useful to have a similar construct. 1. Name bindings local to an expression: roots = ([(-b-r)/(2*a), (-b+r)/(2*a)] where r = sqrt(b*b-4*a*c)) 2. Names bound as part of an expression and also available in a following suite: if m where m = pattern.match(text): do_something_with(m) There's a problem, though -- if "where" in an expression creates bindings local to that expression, they're not going to be visible in the rest of a comprehension or compound statement. So I'm thinking that there would actually be three distinct usages of "where": A. As a clause in a comprehension, where it creates bindings local to the comprehension and is treated on the same footing syntactically as "for" and "if" clauses. B. In an expression, surrounded by parentheses for disambiguation. Bindings are visible only within the parentheses. C. Following the expression of an "if" or "while" statement. Bindings are visible within the preceding expression and the following suite. Note that case B avoids the issue of whether expression-local bindings affect the LHS of an assignment, because in a[x] = (x * 2 where x = 17) the "x" bound by the where-clause is clearly restricted to the part in parentheses. This would be a syntax error: a[x] = x * 2 where x = 17 # illegal -- Greg

The syntax you propose is already in the Alternate syntax and there is an implementation at https://github.com/thektulu/cpython/tree/where-expr Already discussed, but no conclusion, for me I see two different proposals, the "[y for x in range(5) with y = x+1]" in comprehensions list, and the case in any expression "print (y with y = x+1)", as discussed the second case corresponds to a simple assignment so is less useful, but if the scope is local to the expression, that'd be useful : print(y with y = x+1); print(y) # NameError But I like it :) Le 3 mars 2018 02:36, "Greg Ewing" <greg.ewing@canterbury.ac.nz> a écrit :

On 3 March 2018 at 11:36, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I know Guido is on record as not wanting to allow both "for name in sequence" and "for name = expr" due to that being a very subtle distinction between iteration and simple assignment (especially given that Julia uses them as alternate spellings for the same thing), but I'm wondering if it may be worth considering such a distinction in *with statements*, such that we allowed "with name = expr" in addition to "with cm as name" (where "name = expr" is just an ordinary assignment statement that doesn't trigger the context management protocol). The related enhancement to comprehensions would then be to allow with clauses to be interleaved between the for loops and the if statements, such that you could write things like: pairs = [(f(y), g(y)) for x in things with y = h(x)] contents = [f.read() for fname in filenames with open(fname) as f] while still preserving the property where comprehensions can be correctly interpreted just by converting each of the clauses to the corresponding statement form. Even without the "with name = expr" change, allowing with clauses in comprehensions would let you do (by way of a suitably defined "bind" CM): pairs = [(f(y), g(y)) for x in things with bind(h(x)) as y] Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan writes:
pairs = [(f(y), g(y)) for x in things with y = h(x)] contents = [f.read() for fname in filenames with open(fname) as f]
This is horrible. I think Julia is just weird: in normal English we do distinguish between equality and membership. "x in y" is a very different statement from "x = y". I think even Guido would come around to the view if it were implemented (assuming not "over his dead body"). But the semantics of "x = y" and "y as x" in English are both pretty much the copula. It's hard enough to stay aware that there be dragons in a context manager; if "with" could denote a simple (local) binding, it would require conscious effort.
This is *much* better. But suppose you wanted to have *several* bindings. Would this syntax allow "destructuring" of tuples (as the for clause will do): pairs = [(f(x) + g(y), f(x) - g(y)) for w, z in pairs_of_things with bind((h(w), k(z)) as (x, y)] ? This is a question about all the proposals for local binding, I think. Steve

On 3 March 2018 at 11:36, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I'll note that a significant benefit of this approach over the PEP 572 approach is that it would be amenable to a comprehension style scope-management solution: these expressions could create an implicitly nested function and immediately call it, just as 3.x comprehensions do. Adopting such an approach would *dramatically* lower the impact that hiding the bindings from the surrounding scope would have on the overall name resolution semantics. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 3 March 2018 at 07:45, Nick Coghlan <ncoghlan@gmail.com> wrote:
While I'm *still* not sure any of this provides enough benefit to be worth doing, I will say that this proposal feels far less complicated than PEP 572. I don't particularly like the extension to if and while statements (and it's been mentioned that there are remarkably few cases where the value you want to capture is the actual condition rather than a value the condition tests) but otherwise I'm OK with it (call me -0 on the proposal without if/while, and -0.5 on the if/while part). Paul

Le 3 mars 2018 08:45, "Nick Coghlan" <ncoghlan@gmail.com> a écrit : On 3 March 2018 at 11:36, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I'll note that a significant benefit of this approach over the PEP 572 approach is that it would be amenable to a comprehension style scope-management solution: these expressions could create an implicitly nested function and immediately call it, just as 3.x comprehensions do. Adopting such an approach would *dramatically* lower the impact that hiding the bindings from the surrounding scope would have on the overall name resolution semantics. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ As I said, allowing this syntax (whether with a new keyword like 'where' or reusing something like 'with' to write print(y with y = x+1)) in any expression (not only after a "for" in a comprehension list) is useful only if a local scope is created (otherwise, it would be the same as simple assignement but in reverse ordre). One could see : print(y with y = x+1) As a shortcut for : print(next(y for y in [ x+1 ])) The same as this : [y for x in range(5) with y = x+1] being a shortcut for : [y for x in range(5) for y in [ x+1 ]] As said, allowing both use cases would lead to two different ways to write : [y for x in range(5) with y = x+1] vs [y with y = x+1 for x in range(5)] But that is not really an issue, it's logically different (is the y a conclusion of the iteration or a hidden lambda in the expression?).

Robert Vanden Eynde wrote:
Or more straightforwardly, print((lambda y: y)(x + 1)) This is how the semantics of let-type constructs is often defined in lambda-calculus-inspired languages such as Scheme and Haskell, and it suggests a viable implementation strategy. -- Greg

On Sat, Mar 03, 2018 at 02:36:39PM +1300, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
``where`` is a very popular name in code related to SQL. Take for example ORMs: https://github.com/sqlobject/sqlobject/search?l=Python&q=where&type=&utf8=%E2%9C%93 https://github.com/zzzeek/sqlalchemy/search?l=Python&q=where&type=&utf8=%E2%9C%93
-- Greg
Oleg. -- Oleg Broytman http://phdru.name/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.

Nathan Goldbaum wrote:
Bummer, I didn't know numpy used it. That puts rather a big dampener on the idea of making it a keyword. :-( Remaining options include: * Make it a keyword only in certain contexts. That's been done before, but only as a temporary measure. Making it a permanent feature seems a bit hackish, and could cause problems for syntax highlighters. * Pick another word that isn't used often. My next choice would be "given", with maybe "letting" as a distant third. (Not just "let", because it doesn't read right after the expression.) * Re-use another keyword. Here "with" seems to be the best choice, but that would entail giving it two wildly different meanings. Guido probably isn't going to like that, since he has already expressed disapproval of doing it with "for". <tangential_ramble> Some people thought Wirth was mad when he made all the keywords in Modula upper-case, but maybe he knew a thing or two. Going back further, Algol required all keywords to be marked specially in some way (that was left up to the implementation). If I ever design another language, I think I'm going to require the source to be HTML, and insist that all keywords be <b>bold</b>. </tangential_ramble> -- Greg

On 03/02/2018 03:43 AM, Chris Angelico wrote:
Looks nice, thanks for the updates!
Alternative proposals =====================
Just to clarify: "bound in the same scope" means exactly that -- if the (EXPR as NAME) is in a comprehension, then it is local /to that comprehension/ (emphasis because I didn't adequately convey that it my initial response to Nick). Still -1 to the PEP as written. (It would be positive for alternative 6. ;) -- ~Ethan~

On 2018-03-02 03:43, Chris Angelico wrote:
After following the discussion here, I think I've come to the conclusion that something like "EXPR with NAME as VALUE" is better, and that's because the "out-of-order" execution there is actually a benefit. The example that gave me the most pause in this thread was Paul Moore's involving the quadratic formula (slightly emended here :-): x = [(-b + sqrt((b**2 - 4*a*c as D))/(2*a), (-b + sqrt(D)/(2*a)] To me this is a classic example of the kind of thing I would want to do with a statement-local binding, so whatever syntax is chosen won't be useful to me if it doesn't nicely work in situations like this. But I don't find the above example nice, for more or less the reason Paul cited: the need to assign the expression "inline" (at the point where it's first defined) creates an asymmetry that masks the fact that it's the same expression being re-used elsewhere --- and it's exactly that re-use that I would want to HIGHLIGHT with a statement-local binding. The alternative with the name binding at the end is much better: x = [(-b + sqrt(D))/(2*a), (-b - sqrt(D))/(2*a) with b**2 - 4*a*c as D] To my mind, it's a real desideratum for a statement-local binding that it should pull the bound expression OUT of the enclosing context. The overall statement gains in readability only if the reader can easily see a shared element across multiple parts. If I can't tell at a glance that the two roots both involve the same value D, there's little point in having a statement-local binding for it. (It still may gain in computational cost, since the expression won't have to be evaluated multiple times, but I see that as a much less important benefit than readability.) Also note that the version above comes perilously close to the existing solution with a regular local variable: D = b**2 - 4*a*c x = [(-b + sqrt(D))/(2*a), (-b - sqrt(D))/(2*a)] The only difference is that now D is "leaked" to following code. Nick Coghlan has argued that there's no point in an inline-assignment construct if it's not somehow local, since a big part of its purpose is to simplify reasoning by avoiding any stomping on "real" local variables. But if that's the case, maybe what we want is actually another thing that's been discussed many times on this list, namely something like a with-block that can define "super-local" variables that disappear at the end of the block: with b**2 - 4*a*c as D: x = [(-b + sqrt(D))/(2*a), (-b - sqrt(D))/(2*a)] This the same as my earlier version with "with", except the with clause comes at the beginning. There's no need to go into extreme detail here on these proposals as they're not really what's proposed by this PEP. But my point is that, from my perspective, they have something crucial that the current proposal lacks: they explicitly separate the *definition* of the shared expression from its *use* within the statement. Having to do the name-binding inline at the place where the re-used expression happens to occur makes the overall construct LESS readable for me, not more, so I'm -1 on the current proposal. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On Sat, Mar 3, 2018 at 5:30 AM, Brendan Barnwell <brenbarn@brenbarn.net> wrote:
Maybe they should be. There are two halves to this proposal: 1) A syntax for capturing an expression with a name binding 2) A scoping system for statement-local names. I'm already seeing some support for the first half without the second ("just make it a regular assignment"). Do we need two completely separate PEPs to handle these two proposals? They could link to each other saying "this could work well with that", but they have independent value. Statement-local names without a syntax for name binding would be useful in basically two contexts - the "with" syntax given here, and "except Exception as e:", which currently uses a bit of a weird hack of unbinding the existing name. But the 'with' statement would then have a messy dual form. It can be used for name bindings, and it can also be used for resource management. The easiest way to handle this is: def object.__enter__(self): return self def object.__exit__(self, *a): pass But I don't know that people want that. ChrisA

(1) I am concerned with the proposal's ability to introduce variables with a new broader kind of multi-line scope not seen anywhere else in Python. It is difficult to reason about, particularly in constructs like lambdas and inline def functions. Limiting scope to the very same line is great:
stuff = [[(f(x) as y), x/y] for x in range(5)]
However I advocate that named expressions should ONLY be usable in the header of statements that enclose other statements. Thus I would expect the following to be an error:
if (re.match(...) as m): print(m.groups(0)) # NameError: name 'm' is not defined
while (sock.read() as data): print("Received data:", data) # NameError: name 'data' is not defined
Using the named expression in other parts of the statement header is still fine:
if (re.match(...) as m) is not None and m.groups(1) == 'begin': ...
In summary, -1 from me for introducing a new kind of sublocal scope. +0 from me for keeping the scope limited to the header of the statement. Predictable conservative semantics. (2) If there WAS a desire to allow a named expression inside the substatements of a compound statement, I would advocate that the named expression just be treated as a regular (usually-)local variable assignment, with scope that extends to the entire function. This would imply that the variable would appear in locals(). Under this interpretation, the following would be okay:
I would be -0 for using a regular local scope for a named expression. Predictable semantics but leaks the expression to the wider scope of the function. (3a) With a header-limited scope (in proposal #1 above), I advocate that a named expression should NOT be able to shadow other variables, giving a SyntaxError. I can't think of a reasonable reason why such shadowing should be allowed, and preventing shadowing may eliminate unintentional errors. (3b) With a local scope (in proposal #2 above), a named expression simply replaces any preexisting local with the same name. (Or it would replace the global/nonlocal if a global/nonlocal statement was in use.) There is no shadowing per-se. Cheers, -- David Foster | Seattle, WA, USA On Fri, Mar 2, 2018 at 3:43 AM, Chris Angelico <rosuav@gmail.com> wrote:
-- David Foster

On Sat, Mar 17, 2018 at 5:49 PM, David Foster <davidfstr@gmail.com> wrote:
Header-limited scope is hard to define. Do you mean expression-local? (Also hard to define.) Do you mean local to one line of source code? Definitely not. And what happens with a 'for' loop - part of its header gets run after each loop iteration but still kinda references stuff that was done once-only before the loop started. ChrisA

I mean approximately local to one line of source code. Perhaps the unpopular opinion based on your reaction. :) More specifically, for a simple statement (with no trailing colon), there is one scope enclosing everything in the statement. For a compound statement, composed of multiple clauses, where each clause has a header (ending with a colon) and a suite, there are N non-overlapping scopes, one scope for each of the N clause headers. The scope is limited to the header only and does not include the suite. In considering a 'for' loop, I'd advocate for keeping the scope of the expression_list separate from the target_list, since I can't think of a reasonable case where the target_list would want to reference something from the expression_list. So the following code would have a NameError for magic_index in the target_list:
That's pretty bizarre code, using a fixed index of an array as an iteration variable. The only other type of 'for' loop target that might try to use a named expression from the expression_list is a slice expression, which would be even more bizarre code. Best to make bizarre cases into errors. Cheers, -- David Foster | Seattle, WA, USA On Sat, Mar 17, 2018 at 12:13 AM, Chris Angelico <rosuav@gmail.com> wrote:
-- David Foster

Disclaimer: I skimmed/searched through the PEP 572 threads (or should I say "literature"?) and did not find a discussion of the following point. If it has been discussed already, I'd be glad to be pointed to it. I am aware that Python, in contrast to C-like languages, has chosen not to treat assignments (=) as an expression with a value. The motivation for this is to avoid bugs due to confusion with the equality operator (==). But wouldn't it be a good alternative to PEP 572 to *add* an assignment operator that is an expression to Python, and is distinct from "=", for example ":="? Then, instead of PEP 572's: stuff = [[(f(x) as y), x/y] for x in range(5)] one could write stuff = [[(y := f(x)), x/y] for x in range(5)] In difference to PEP 572, the variable y would not be statement-local, which IMHO would be a welcome simplification. (PEP 572 introduces a third class of variables to Python.) Overall, it seems to me that introducing a new operator ":=" would serve the same purpose as PEP 572, but in a simpler and arguably cleaner way, while eliminating the risk of confusion with "==". The operator "=" would be left around, but could be deprecated in Python 5 and removed in Python 6. It would certainly suit a language that is widely used in education to sharpen the distinction between assignment and equality operators. Cheers, Christoph

I also think it's fair to at least reconsider adding inline assignment, with the "traditional" semantics (possibly with mandatory parentheses). This would be easier to learn and understand for people who are familiar with it from other languages (C++, Java, JavaScript). On Fri, Mar 23, 2018 at 9:34 AM, Christoph Groth <christoph@grothesque.org> wrote:
-- --Guido van Rossum (python.org/~guido)

On 03/23/2018 07:38 PM, Chris Angelico wrote:
On Sat, Mar 24, 2018 at 3:58 AM, Guido van Rossum wrote:
On Fri, Mar 23, 2018 at 9:34 AM, Christoph Groth wrote:
I'm certainly hoping PEP 572 is rejected so we can have a follow-up PEP that only deals with the assignment-as-expression portion. No offense intended, Chris! :) In fact, maybe you could write that one too, and then have an accepted PEP to your name? ;) -- ~Ethan~

On Sat, Mar 24, 2018 at 2:09 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Okay, maybe I *do* need to split them. There are other uses for statement-local names, so they're both potentially viable. Would that be the best way forward? If I do, sooner would be better than later - I'd want to grab PEP 573 while it's still available. ChrisA

On 24 March 2018 at 13:15, Chris Angelico <rosuav@gmail.com> wrote:
Inline assignment (whether using the "expr as name" syntax or a new dedicated "x := y" one) certainly comes up often enough that it would be worth writing up independently of any of the statement local scoping proposals. One benefit of " x := y" is that it would avoid ambiguity in the with statement and exception handling cases: with cm := cm_expr as enter_result: ... try: ... except exc_filter := exceptions_to_catch as caught_exc: ... In comprehensions and generator expressions, we'd need to explain why inline assignments in the outermost iterator expression leak but those in filter expressions, inner iterator expressions, and result expressions don't. It would also result in two different ways to handle traditional assignments: x = expr x := expr Perhaps ":=" could be explicitly restricted to only single names on the LHS, without any of the clever unpacking features of full assignment statements? Unlike full assignment statements, assignment expressions also wouldn't have anywhere to put a type annotation. That way, there'd technically be overlap between the two in the simple case of assigning to a single name, but in other circumstances, it would be obvious which one you needed to use. Cheers, Nick. P.S. Pascal was one of the first languages I used to write a non-trivial application (a game of Battleships), so I'm predisposed towards liking ":=" as an assignment operator :) -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 03/24/2018 01:35 AM, Nick Coghlan wrote:
In comprehensions and generator expressions, we'd need to explain why inline assignments in the outermost iterator expression leak but those in filter expressions, inner iterator expressions, and result expressions don't.
I don't understand -- could you give an example? -- ~Ethan~

On Sun, Mar 25, 2018 at 2:48 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Let's suppose we have assignment expressions. I'm going to use "(expr as name)" syntax for this example. a = [(1 as b) for c in (d as e) if (2 as f)] Which of these six names is local to the comprehension and which can leak? Due to the requirements of class scope, 'd' must be looked up in the outer scope. That means that its corresponding 'as e' must also land in the outer scope. 'a', of course, is in the outer scope. The other four are local to the inner function that implements the comprehension. ChrisA

On Sun, Mar 25, 2018 at 2:59 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
No, it's looked up inside func, and the value is found outside of func. Consider: d = 9 def func(): e = d d = 1 What's happening with a comprehension is more like: d = 9 def func(_iter): ... body of comprehension func((d as e)) which is looking it up _outside_ the function, then passing it as a parameter. ChrisA

On 03/24/2018 09:00 PM, Chris Angelico wrote:
On Sun, Mar 25, 2018 at 2:59 PM, Ethan Furman wrote:
On 03/24/2018 08:51 PM, Chris Angelico wrote:
That is invalid Python.
Looks like a buggy implementation detail. Any assignments that happen inside a listcomp should be effective only inside the listcomp. -- ~Ethan~

On 25 March 2018 at 14:08, Ethan Furman <ethan@stoneleaf.us> wrote:
Looks like a buggy implementation detail. Any assignments that happen inside a listcomp should be effective only inside the listcomp.
No, the fact that the expression defining the outermost iterable gets evaluated in the outer scope is behaviour that's explicitly tested for in the regression test suite. The language reference spells out that this is intentional for generator expressions, where it has the added benefit of reporting errors in the outermost iterable expression at the point where the genexp is defined, rather than at the point where it gets iterated over: https://docs.python.org/3/reference/expressions.html#generator-expressions Independently of the pragmatic "getting them to work sensibly at class scope" motivation, comprehensions inherit those semantics by way of the intended semantic equivalence between "[x for x in sequence]" and "list(x for x in sequence)". Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 03/24/2018 09:24 PM, Nick Coghlan wrote:
Thank you (everyone!) for your patience. Using the latest example from Angelico and myself: --> d = 9 ... def func(_iter): ... ... body of comprehension --> func((d as e)) The sticking point is the `func((d as e))`, which to my mind should happen inside the comprehension, but needs to happen outside -- and the reason it has to happen outside is so that the interpretor can verify that `d` actually exists; however, I think we can have both: --> def func(_iter): ... ... ... e = d ... ... --> d --> func() This way, the assignment does not leak, but the referenced name is still looked up and verified to exist outside the function call. I don't know how easy/difficult that would be to implement. At this point a simple confirmation that I understand and that in theory the above would solve both issues is what I'm looking for. Or, of course, the reasons why the above would not, in theory, work. -- ~Ethan~

On 28 March 2018 at 00:52, Ethan Furman <ethan@stoneleaf.us> wrote:
Just what Chris said earlier: the expression defining the outermost iterable can be arbitrarily complex, and what you're suggesting would only be feasible for a top-level name binding, not for nested ones. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 25 March 2018 at 13:51, Chris Angelico <rosuav@gmail.com> wrote:
Adding qualifiers to the names to show which names would be resolved where: outer_a = [(1 as inner_b) for inner_c in (outer_d as outer_e) if (2 as inner_f)] print(outer_a, outer_d, outer_e) # Works print (inner_b, inner_c, inner_f) # Name error In current Python,with the outer scope's name bindings being read-only inside a comprehension, this difference in scope resolution really only matters at class scope, and when using the two-namespace form of exec and eval (since method semantics result in that impacting which names the comprehension can see). At function scope, referenced names from the outer scope get resolved as closure references, while at module scope they get resolved as global references, so you have to look at the generated bytecode to see the differences. That all changes once we start allowing name binding (in any form) at the level of expressions rather than statements: regardless of the nature of the outer scope, the exact structure of the implicit comprehension scope becomes relevant whenever you bind names inside the comprehension. Now, the notable *distinction* here relative to the old iteration variable leakage is that doing a name binding in the outermost iterable expression can always be refactored to use an ordinary assignment statement instead: outer_e = outer_d outer_a = [(1 as inner_b) for inner_c in outer_d if (2 as inner_f)] print(outer_a, outer_d, outer_e) # Works print (inner_b, inner_c, inner_f) # Name error This formulation also makes it clear that "outer_e" will only resolve inside the comprehension if "outer_d" already resolves. This means that if we did go down the expression-level-assignments-are-just-regular-name-binding-operations path, then using the embedded assignment form in such cases could be discouraged as misleadingly ambiguous by style guides and linters, without being outright prohibited by the compiler itself. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
Why would "=" have to be kept for anything else but backwards compatibility and saving keystrokes? The expression var: annotation := value could assign a value to a name and it would have the corresponding value. I guess that it's problematic for the language grammar to allow things like for i in (var: annotation := value): print(i) In that case such constructions could be declared illegal.
Having an assignment operator that is optically distinct from '=' is IMO a strong point of Pascal. For Python, an even better assignment operator would be IMO "<-", but this is not possible without breaking backwards compatibility, and also it's a bit difficult to type on an English keyboard.

Chris Angelico wrote:
Thank you; both of these have now been incorporated into the document.
Thanks! Just a small comment. You wrote in the PEP draft:
I don't understand what you mean here. If the (y as x) syntax is to have, as you say, "the exact same semantics as regular assignment", then assignments inside list comprehensions would always "leak". But this is a good thing, because this is consistent with how Python behaves. Christoph

On Sat, Mar 24, 2018 at 8:07 PM, Christoph Groth <christoph@grothesque.org> wrote:
Except that a list comprehension is implemented using an inner function. Very approximately: x = [n * m for n in range(4) for m in range(5)] def <listcomp>(iter): ret = [] for n in iter: for m in range(5): ret.append(n * m) return ret x = <listcomp>(iter(range(4)) So the first (outermost) iterable is actually evaluated in the caller's scope, but everything else is inside a subscope. Thus an assignment inside that first iterable WILL leak into the surrounding scope; but anywhere else, it won't. ChrisA

On 24 March 2018 at 09:18, Chris Angelico <rosuav@gmail.com> wrote:
Wow, that's subtle (in a bad way!). I'd much rather that assignments don't leak at all - that seems to me to be the only correct design, although I understand that implementation practicalities mean it's hard to do. There's a lot of context snipped here. Is this about the variant that just does assignment without the new scope? If it is, then is there a similar issue with the actual proposal, or is that immune to this problem (I suspect that it's not immune, although the details may differ). Paul

On 24 March 2018 at 11:55, Chris Angelico <rosuav@gmail.com> wrote:
Understood. But the current Python semantics doesn't have any way of binding a name as part of that outermost iterable. It's the interaction of the new feature (name binding in expressions) with the existing implementation (the first iterable is outside the constructed scope for the comprehension) that needs to be clarified and if necessary modified to avoid nasty consequences. Paul

On 24 March 2018 at 21:49, Paul Moore <p.f.moore@gmail.com> wrote:
We can't do that because it would make comprehensions nigh-unusable at class scope (or, equivalently, when using exec with a separate locals namespace): class C: _sequence = "a b c d".split() _another_sequence = [f(item) for item in _sequence] "_sequence" needs to be resolved in the class scope and passed in as an argument in order for that to work, as the nested scope can't see it directly (since the implicit nested scope works like any other method definition for name resolution purposes).
PEP 572 is *mostly* immune, in that it's only the rest of the same statement that can see the name binding. The variant that *doesn't* introduce statement local scoping just leaks outright into the surrounding scope. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Chris Angelico wrote:
Indeed, the Python language reference section 6.2.4 says: "Note that the comprehension is executed in a separate scope, so names assigned to in the target list don’t “leak” into the enclosing scope." As far as I can tell this has only consequences for the loop index variables of the comprehension, because these are the only names that are assigned values inside the comprehension. After all, assignment is currently a statement in Python, so it's not possible inside a list comprehension to assign to other names. (Or am I overlooking something here?)
It seems to me that this is a CPython implementation detail. Couldn't x = [n * m for n in range(4) for m in range(5)] be equally well equivalent to def <listcomp>(): ret = [] for n in range(4): for m in range(5): ret.append(n * m) return ret x = <listcomp>() As far as I can tell, with today's Python both implementations (yours and mine) are indistinguishable by the user. Since Python 3 decided to put comprehensions in a scope of their own, that means that if an assignment operator ":=" is added to Python the list comprehension [(t := i**2, t**2) for i in (r := range(10))] should neither leak r, nor t nor i. Seems fine to me! Christoph

On 24 March 2018 at 23:29, Christoph Groth <christoph@grothesque.org> wrote:
They can be distinguished, just not at module or function scope. To give a concrete example: ========== >>> class C: ... sequence = range(10) ... listcomp = [x for x in sequence] ... def works(data): ... return list(data) ... from_works = works(sequence) ... def fails(): ... return list(sequence) ... >>> C.listcomp [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> C.from_works [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> C.fails() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 8, in fails NameError: name 'sequence' is not defined ========== I think I did have an implementation that didn't do the "outermost iterator is evaluated in the outer scope" dance early on (back when I was still working on the initial version of the iteration variable hiding, before it was merged to the Py3k branch), but quickly changed it because relying solely on closures broke too much code (in addition to the class namespace case, you can create a situation with similar constraints by passing a separate locals namespace to exec). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

2018-03-24 17:14 GMT+03:00 Nick Coghlan <ncoghlan@gmail.com>:
But is the example with the class appropriate in this context, it seems that it is well understood why this example will fail, and `C.fails()` should at least be a `@classmethod`:) From the docs:
And if we cross out class, exec and eval cases - they don't work now, they will not be affected in future. Are not these examples equivalent? With kind regards, -gdg

On 25 March 2018 at 01:03, Kirill Balunov <kirillbalunov@gmail.com> wrote:
I don't follow what you're trying to say here. Comprehensions *do* work with class bodies, two-namespace exec, and two-namespace eval, and one of the consequences of the *way* they work that proposals for expression level name binding will all have to deal with is that the outermost iterable expression is evaluated in the containing scope rather than the implicitly nested one. Statement local name bindings would restrict any resulting name leakage from the outermost iterable to the affected statement, while regular name bindings would affect the entirety of the containing scope. The latter behavour is of the larger downsides of having expression level name bindings work the same way assignment statements do, since it recreates a variant of the original iterator variable name leakage that the introduction of the implicitly nested scope eliminated. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 25 March 2018 at 02:34, Eric Fahlgren <ericfahlgren@gmail.com> wrote:
Everything except the outermost iterator is evaluated in the implicitly nested scope, so comprehensions at class scope have the restriction that only the outermost iterator can access names defined in the class scope. It turned out that was enough to preserve compatibility with most of the comprehensions that folks actually use at class scope. For those rare cares where it isn't, the typical resolution is to either define a helper function, or else switch to a regular for loop. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sat, Mar 24, 2018 at 7:08 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Just in case anyone wonders, I don't think the special rules around class scope count are a wonderful feature. 28 years ago it was the best we could do, and by the time we realized its problems -- all rare edge cases for sure, but unintuitive and hard to debug when they strike -- we were tied by backward compatibility. And we still are. (At the time there were no nested scopes, there was just local, global, builtins; class scope was treated as a function scope and that was that.) (I have a lot more to say about PEP 572 but I'll do it in the "take three" thread.) -- --Guido van Rossum (python.org/~guido)

Nick Coghlan wrote:
Thanks a lot for this example. So you mean that while listcomp = [x for x in sequence] works above listcomp = [(y,x) for y in sequence for x in sequence] doesn't (but it would if the class was replaced by a function). That's indeed a bit strange, and I would consider it somewhat of a wart of the language. But as far as I can tell remaining compatible with the above behavior does not force us to leak assignments from the outermost scope of a comprehension. I.e. there's nothing in the language currently that forces listcomp = [x for x in (r := sequence)] to leak the name "r". Granted, it's a bit strange if in the above line the name "sequence" is evaluated in class scope but the name "r" is set in the comprehension scope, but since currently there is no way to assign values to names in comprehensions this "hybrid" behavior would be backwards-compatible, and less surprising than leaking "r". This could be fixed if backwards compatbility may be ever broken again, but until then I don't expect it to be a problem.

On Sun, Mar 25, 2018 at 8:31 PM, Christoph Groth <christoph@grothesque.org> wrote:
It seems fine in a simple example, but remember, an assignment expression can be used ANYWHERE in an expression. Consider: listcomp = [x for x in obj[r := f()][x + r] ] Which parts happen in the inner scope and which in the outer scope? If 'r' is created in a subscope, it has to be a subscope of the outer scope; if it's not subscoped, it has to be directly in the outer scope. It can't sanely be in the inner scope. ChrisA

Chris Angelico wrote:
The example you give is indeed difficult to understand, but all programming languages, even Python, allow to write confusing code. Already today we can have listcomp = [x for x in obj[f()][x + r]] and, while valid, that's hardly an example worthy of emulation. Compared to this, the example that you give is indeed even more treacherous, because innocent people could assume that the 'r' in '[x + r]' is the one set just before with ':='. But is this obscure behavior really a problem? A bug can only happen if 'r' is also defined in the surrounding scope, otherwise there will be an error. And if this is indeed considered a problem, such confusing situations could be detected and flagged as an error by the Python compiler until the underlying inconsistency in the language is fixed. I think that it's a helpful guideline to imagine what the ideal behavior should be if we were not constrained by backwards compatibility, and then try to follow it. In the case at hand, we all seem to agree that the fact that the outermost iterator of a comprehension is evaluated in the surrounding scope is somewhat of a wart, although one that is rarely visible. The existence of a wart shouldn't pull us further into the wrong direction.

On 25 March 2018 at 22:44, Christoph Groth <christoph@grothesque.org> wrote:
There's no such agreement, since generator expressions have worked that way since they were first introduced almost 15 years ago: https://www.python.org/dev/peps/pep-0289/#the-details It's much easier to see the difference with generator expressions, since the evaluation of the loop body is delayed until the generator is iterated over, while the evaluation of the outermost iterator is immediate (so that any exceptions are more easily traced to the code responsible for them, and so that they don't need to create a closure in the typical case). With comprehensions, the entire nested scope gets evaluated eagerly, so it's harder to detect that there's a difference in the evaluation scope of the outermost iterator in normal use. That difference *does* exist though, and we're not going to tie ourselves into knots to try to hide it (since the exact same discrepancy will necessarily exist for generator expressions, and semantic consistency between genexps and the corresponding comprehensions is highly desirable). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
I was referring to [1], but I see now that things are more subtle. Guido didn't express a dislike for the special treatment of the outermost iterator, just (if I understand correctly) for the consequences of this in class scope. [1] https://mail.python.org/pipermail/python-ideas/2018-March/049461.html
Thanks a lot for the explaination. I know many of the more obscure aspects of Python, but I wasn't aware of this. I'm still not sure whether I agree that it is a good idea to treat the evaluation of the outermost iterator differently from the rest. It can be especially confusing that the outermost iterator appears in the middle of the generator expression, surrounded by code that is evaluated in the inner scope. But I don't want to start a futile discussion about backwards-incompatible changes here.
With tying into knots you mean my suggestion to bind both 'a' and 'b' at the internal scope in gen = (i+j for i in x[a := aa] for j in (b := bb)) even if x[a := aa] is evaluated in the outer scope? I tend to agree that this is indeed too arcane. But then how about making ":=" work always at the outer scope (and thus reserve the inner scope of generators only for the index variables), i.e. make the above equivalent to: def g(it): nonlocal b for i in it: for j in (b := bb): yield i+j gen = g(iter(x[a := aa])) That would be less confusing and indeed it could even turn out useful to be able to access names that were bound inside the generator outside of it. Note that the use of ":=" inside generator expressions would be optional, so it can be considered fair to treat such assignments differently from the implicit assignments to the loop variables.

FWIW, I thought another way which provides cache object library, it seems to just work in some cases. But it doesn't create statement local scope and might be difficult to read because looks ordinary expression doing magic. Chris, would you append the library to alternative proposal section? class Cache: """ Return a object which stores a value called with a keyword and immediately return it, then get the value using the attribute. """ def __call__(self, **kwargs): (k, v), = kwargs.items() # require one keyword setattr(self, k, v) return v c = Cache() print(c(spam="ham")) # => ham print(c.spam) # => ham L = [c(y=f(x)) + g(c.y) for x in range(5)] num = int(c.s) if c(s=eggs()).isdigit() else 0 if c(match=search(string)) is not None: print(c.match.groups()) while c(command=input()) != "quit": print("command is", c.command) -- Masayuki 2018-03-02 20:43 GMT+09:00 Chris Angelico <rosuav@gmail.com>:

On 2 March 2018 at 11:43, Chris Angelico <rosuav@gmail.com> wrote:
This is a distinct improvement - thanks for incorporating the varied feedback so well. I still remain -1, though. Reasons scattered through the other thread but mostly relating to the fact that there are subtle differences between statement-local names and other names in Python (notably that you can't close over them). I don't think the benefits of the proposal are sufficient to justify introducing a new similar-but-subtly-different type of name. Paul

On Fri, Mar 2, 2018 at 10:56 PM, Paul Moore <p.f.moore@gmail.com> wrote:
That's fine :) I'm under no illusions that everyone will adore this proposal (hey, I'm no more than +0.5 on it myself). The greatest goal of this PEP is to record the arguments for and against each of the viable alternatives, as a long-term document. ChrisA

On 02/03/18 11:43, Chris Angelico wrote:
I haven't said this yet, so thanks Chris for putting this all together. Even if the result is a rejected PEP, at least we have everything in one place. [snip]
This (and the equivalent in while loops) is the big win in the PEP, in my opinion. The number of ugly loops I've had to write in Python because I can't write "while (something_to_do() as event):"... +1 on this.
# Using a statement-local name stuff = [[(f(x) as y), x/y] for x in range(5)]
As Paul said, the asymmetry of this bothers me a lot. It doesn't read naturally to me. -1 on this.
I wouldn't worry too much about this case. Anyone gratuitously reusing names like that deserves all that will be coming to them.
Honestly I prefer this syntax for comprehensions. It doesn't read perfectly but it's good enough (though I am a mathematician by original training, so set notation works for me anyway), and the syntax is clear and limited. I'm not sure the case for fully general statement-local variables has been made. So, counter-proposal(s): 1. Allow "(f() as a)" in the conditions of "if" and "while" statements, after some arguing as to whether "a" is a special snowflake or just a normal local variable. 2. Add a "with" clause to comprehensions to make comprehension-local variables (presumably the same class of thing as the iteration variables). -- Rhodri James *-* Kynesim Ltd

+1 on extracting the big win for "if" and "while" (the regex case is wonderul). It would be see as an "extended if/while" rather than a general statement assignation. +1 on list comprehensions, even if I prefer the [(y, x/y) with y = f(x) for x in range(5)] or [(y, x/y) for x in range(5) with y = f(x)] syntax (more close to "for y in [ f(x) ]". Le 2 mars 2018 15:54, "Rhodri James" <rhodri@kynesim.co.uk> a écrit : On 02/03/18 11:43, Chris Angelico wrote:
I haven't said this yet, so thanks Chris for putting this all together. Even if the result is a rejected PEP, at least we have everything in one place. [snip] # Compound statements usually enclose everything...
This (and the equivalent in while loops) is the big win in the PEP, in my opinion. The number of ugly loops I've had to write in Python because I can't write "while (something_to_do() as event):"... +1 on this. # Using a statement-local name
stuff = [[(f(x) as y), x/y] for x in range(5)]
As Paul said, the asymmetry of this bothers me a lot. It doesn't read naturally to me. -1 on this. 1. What happens if the name has already been used? ``(x, (1 as x), x)``
I wouldn't worry too much about this case. Anyone gratuitously reusing names like that deserves all that will be coming to them. Alternative proposals
Honestly I prefer this syntax for comprehensions. It doesn't read perfectly but it's good enough (though I am a mathematician by original training, so set notation works for me anyway), and the syntax is clear and limited. I'm not sure the case for fully general statement-local variables has been made. So, counter-proposal(s): 1. Allow "(f() as a)" in the conditions of "if" and "while" statements, after some arguing as to whether "a" is a special snowflake or just a normal local variable. 2. Add a "with" clause to comprehensions to make comprehension-local variables (presumably the same class of thing as the iteration variables). -- Rhodri James *-* Kynesim Ltd _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

2018-03-02 7:03 GMT-08:00 Robert Vanden Eynde <robertve92@gmail.com>:
I wonder if we could have a more limited change to the language that would allow only the as/while use cases. Specifically, that means we could do: while do_something() as x: print(x) if re.match(r'.*', text) as match: print(match.groups()) Parentheses would no longer be necessary for syntactic ambiguity, and there is no real need for new scoping rules—we could just create new locals. This would alleviate some of the problems with the current proposal, such as complicated scoping rules and ugly syntax in comprehensions.

On Sat, Mar 3, 2018 at 5:27 AM, Jelle Zijlstra <jelle.zijlstra@gmail.com> wrote:
The trouble with this is that it's extremely restricted. It works for the regex case, because re.match() has been specifically written to function as either "return a match object" or "return true/false to indicate a match", by guaranteeing to return a truthy object or None, and nothing else. It would NOT work for anything where the bool() of the desired object doesn't exactly match the loop's condition. For instance, if do_something() returns None when it's done, but might return an empty string during operation, the correct condition is "while do_something() is not None" - and you can't capture the whole condition any more. To be able to write "while (do_something() as x) is not None:", you have to have the more general syntax that's used in this PEP. ChrisA

On 02/03/18 18:27, Jelle Zijlstra wrote:
2018-03-02 7:03 GMT-08:00 Robert Vanden Eynde <robertve92@gmail.com>:
Guys, please don't email to me *and* the mailing list. Getting two copies of your deathless prose makes me less likely to pay attention to you, not more. -- Rhodri James *-* Kynesim Ltd

@Rhodri, this is what Everybody does because you hit the "reply to all" button, but, we don't receive two copies on the mail, I don't know why (but that's a good thing). Le 2 mars 2018 20:48, "Rhodri James" <rhodri@kynesim.co.uk> a écrit : On 02/03/18 18:27, Jelle Zijlstra wrote:
2018-03-02 7:03 GMT-08:00 Robert Vanden Eynde <robertve92@gmail.com>:
Guys, please don't email to me *and* the mailing list. Getting two copies of your deathless prose makes me less likely to pay attention to you, not more. -- Rhodri James *-* Kynesim Ltd

On Sat, Mar 3, 2018 at 6:55 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
If you're using a poor-quality mail client, you may have to fix things manually. (Or get a better mail client - it's up to you.) If "Reply-All" sends to both the sender and the list, either look for a "Reply-List" button, or manually delete the sender's address and post only to the list. And please, don't top-post. Again, if your mail client encourages top posting, either override it, or get a better one. ChrisA

Le 2 mars 2018 21:02, "Chris Angelico" <rosuav@gmail.com> a écrit : On Sat, Mar 3, 2018 at 6:55 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
If you're using a poor-quality mail client, you may have to fix things manually. (Or get a better mail client - it's up to you.) If "Reply-All" sends to both the sender and the list, either look for a "Reply-List" button, or manually delete the sender's address and post only to the list. And please, don't top-post. Again, if your mail client encourages top posting, either override it, or get a better one. ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ @Chris @Rohdri (@Jonathan below) For morons like me who didn't know what "top-posting" was, I went on Wikipedia (https://en.m.wikipedia.org/wiki/Posting_style). I think newcomers to mailing list aren't supposed to know all those "de facto standard" (they never used mailing list before ? And I'm 25 years old, not 12), so please don't tell "our mail client is bad, get a new one", I'm using gmail on Android and gmail on Google.com, that's like, very standard. But if you have other mail clients to suggest me, to configure my user interface, feel free to help me, but they will always be people not knowing these good mailing practice (is using html mail a bad practice too ? It used to be). That's why moving to MM3 so that newcomers can use tool a bit more familiar like a forum and will not make the same "mistakes" (top posting, reply only to sender...) over and over again is a good thing. As someone said in their mail comparing web-based to mail, mailing needs configuration. @Jonathan, thanks for the settings, I didn't know about it, it solves this common problem !

On 03/02/2018 12:47 PM, Robert Vanden Eynde wrote:
You looked it up and researched it, so definitely NOT a moron. ;)
Standard, maybe. Good for basic email discussion, nevermind mailing lists? Nope (My humble opinion, of course.)
But if you have other mail clients to suggest me, to configure my user interface, feel free to help me, but they will always be people not knowing these good mailing practice (is using html mail a bad practice too ? It used to be).
The Python mailing lists are plain-text, so html-mail is not great.
Most things worth using require configuration/customization (cars, kitchens, and definitely software). Thanks for persevering! -- ~Ethan~

Le 2 mars 2018 22:03, "Ethan Furman" <ethan@stoneleaf.us> a écrit : On 03/02/2018 12:47 PM, Robert Vanden Eynde wrote: @Chris @Rohdri (@Jonathan below)
You looked it up and researched it, so definitely NOT a moron. ;) I think newcomers to mailing list aren't supposed to know all those "de
Standard, maybe. Good for basic email discussion, nevermind mailing lists? Nope (My humble opinion, of course.) But if you have other mail clients to suggest me, to configure my user
The Python mailing lists are plain-text, so html-mail is not great. That's why moving to MM3 so that newcomers can use tool a bit more familiar
Most things worth using require configuration/customization (cars, kitchens, and definitely software). Thanks for persevering! -- ~Ethan~ _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ Configuration is awesome but having good default helps you not having to tell everyone "configure that, the default is bad" (the "no send duplicate" settings maybe changed default or you misclicked on it). I'm just saying, be comprehensive to newcomer making mistakes, or give them a good man to read when they sign in :)

On Sat, Mar 3, 2018 at 7:47 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
As Ethan says, not a moron. (Though the way he put it, he sounded like Wheatley... and if you don't know what I'm talking about, check out Portal and Portal 2, they're games well worth playing.) One of the beauties of text is that it's easy to research; if someone drops a term like "top-posting", you key that into your search engine, and voila, extra information. This is another advantage of separate standards and common conventions. Everything works with everything else, because it is simple and because nobody has to do everything themselves. How do you use git to manage your CPython source tree? It's exactly the same as using git for anything else, so you don't need Python-specific information, just git-specific information. How do you use a mailing list to discuss proposed language changes in Python? Again, it's generic stuff about mailing lists, so you don't need anything from Python. This is why we work with open standards. Email and mailing lists are an important part of this. ChrisA

Le 2 mars 2018 22:13, "Chris Angelico" <rosuav@gmail.com> a écrit : On Sat, Mar 3, 2018 at 7:47 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
As Ethan says, not a moron. (Though the way he put it, he sounded like Wheatley... and if you don't know what I'm talking about, check out Portal and Portal 2, they're games well worth playing.) One of the beauties of text is that it's easy to research; if someone drops a term like "top-posting", you key that into your search engine, and voila, extra information. This is another advantage of separate standards and common conventions. Everything works with everything else, because it is simple and because nobody has to do everything themselves. How do you use git to manage your CPython source tree? It's exactly the same as using git for anything else, so you don't need Python-specific information, just git-specific information. How do you use a mailing list to discuss proposed language changes in Python? Again, it's generic stuff about mailing lists, so you don't need anything from Python. This is why we work with open standards. Email and mailing lists are an important part of this. ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ As said on the Wikipedia page I shared, """ For a long time the traditional style was to post the answer below as much of the quoted original as was necessary to understand the reply (bottom or inline). Many years later, when email became widespread in business communication, it became a widespread practice to reply above the entire original and leave it (supposedly untouched) below the reply. While each online community <https://en.m.wikipedia.org/wiki/Virtual_community> differs on which styles are appropriate or acceptable, within some communities the use of the “wrong” method risks being seen as a breach of netiquette <https://en.m.wikipedia.org/wiki/Netiquette>, and can provoke vehement response from community regulars. """ De-facto I wouldn't know which convention (of course now I clearly prefer "interleaved posting" for obvious reason... Now that I know how to do it) so a Manual or HowTo when signing in would be an idea :) Of course, I clearly prefer interleaved posting for obvious reasons... now that I know how to produce it using my daily mail client.

Le 2 mars 2018 22:21, "Robert Vanden Eynde" <robertve92@gmail.com> a écrit : Le 2 mars 2018 22:13, "Chris Angelico" <rosuav@gmail.com> a écrit : On Sat, Mar 3, 2018 at 7:47 AM, Robert Vanden Eynde <robertve92@gmail.com> wrote:
As Ethan says, not a moron. (Though the way he put it, he sounded like Wheatley... and if you don't know what I'm talking about, check out Portal and Portal 2, they're games well worth playing.) One of the beauties of text is that it's easy to research; if someone drops a term like "top-posting", you key that into your search engine, and voila, extra information. This is another advantage of separate standards and common conventions. Everything works with everything else, because it is simple and because nobody has to do everything themselves. How do you use git to manage your CPython source tree? It's exactly the same as using git for anything else, so you don't need Python-specific information, just git-specific information. How do you use a mailing list to discuss proposed language changes in Python? Again, it's generic stuff about mailing lists, so you don't need anything from Python. This is why we work with open standards. Email and mailing lists are an important part of this. ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ As said on the Wikipedia page I shared, """ For a long time the traditional style was to post the answer below as much of the quoted original as was necessary to understand the reply (bottom or inline). Many years later, when email became widespread in business communication, it became a widespread practice to reply above the entire original and leave it (supposedly untouched) below the reply. While each online community <https://en.m.wikipedia.org/wiki/Virtual_community> differs on which styles are appropriate or acceptable, within some communities the use of the “wrong” method risks being seen as a breach of netiquette <https://en.m.wikipedia.org/wiki/Netiquette>, and can provoke vehement response from community regulars. """ De-facto I wouldn't know which convention (of course now I clearly prefer "interleaved posting" for obvious reason... Now that I know how to do it) so a Manual or HowTo when signing in would be an idea :) Of course, I clearly prefer interleaved posting for obvious reasons... now that I know how to produce it using my daily mail client. Damn it, I forgot to erase the first paragraph, I'm used to forum app where you can quickly edit a sent message when nobody already read it. By the way, yes, text is wonderful, searchable and archivable, that's one of the reasons to love programming and markup language :)

On Fri, Mar 2, 2018 at 2:56 PM Robert Vanden Eynde <robertve92@gmail.com> wrote:
*de-lurks* There is an option in your personal settings for the list to choose whether to receive duplicate copies of messages sent to both you and the list. It is usually wise to set that to "no". To check this option, go to https://mail.python.org/mailman/options/python-ideas, enter your email address and password (if you don't recall your password, just enter your email address and click "Remind" at the bottom of the page), and click "Log in". From there, you can change a variety of options, including the one about receiving duplicate copies of messages aimed at both you and the list.

On Fri, Mar 2, 2018 at 10:27 AM, Jelle Zijlstra <jelle.zijlstra@gmail.com> wrote:
The "while" case is the only part of the PEP that has any traction with me. It doesn't add any keywords, scope can be identical to "with" and it cleans up a code pattern that is very common. Every one of these comprehension examples has me scratching my head, thinking back a couple hundred (thousand? :) ) posts to Barry's quip, "Sometimes a for loop is just better" (or something along those lines).

On Sat, Mar 3, 2018 at 7:04 AM, Eric Fahlgren <ericfahlgren@gmail.com> wrote:
How often do you have a loop like this where you actually want to capture the exact condition? I can think of two: regular expressions (match object or None), and socket read (returns empty string on EOF). This simplified form is ONLY of value in that sort of situation; as soon as you want to add a condition around it, this stops working (you can't say "while do_something() is not _sentinel as x:" because all you'll get is True). And if you are looking for one specific return value as your termination signal, you can write "for x in iter(do_something, None):".
True, but there's a code smell to an unrolled 'for' loop that could have been a comprehension had it not been for one trivial point of pedantry. So there are advantages and disadvantages to each. ChrisA

On 2018-03-02 12:20, Chris Angelico wrote:
But you could have "while (do_something() as x) is not _sentinel". Not sure how proponents and opponents would react to that. Limiting the SLNB to the beginning of block-level statements seems perverse in a way, but also might cut down on gratuitous overuse mixed into all kinds of weird positions in statements. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On Sat, Mar 3, 2018 at 7:31 AM, Brendan Barnwell <brenbarn@brenbarn.net> wrote:
If we're going to have something that's syntactically valid in an expression, I don't see a lot of value in requiring that it be inside a while or if header. That just leads to confusion and mess down the road. ChrisA

On Fri, Mar 2, 2018 at 12:20 PM, Chris Angelico <rosuav@gmail.com> wrote:
For me, it's all the time. Our geometry modeling database is hierarchical, so you see things like this all over kernel, often with a lot more code than just that one line calculating the cumulative scale factor:
which would turn into

On Sat, Mar 3, 2018 at 1:53 AM, Rhodri James <rhodri@kynesim.co.uk> wrote:
No problem. And I agree, a rejected PEP is still a successful result here. (Am I going to get a reputation for captaining dead PEPs?)
Interesting. I fully expected to get a lot more backlash for the if/while usage, but a number of people are saying that that's the only (or the biggest) part of this proposal that they like. ChrisA

I remain -1 on the PEP, but thank you very much Chris for the great work writing it and the extra clarifications in it. The part I like most about the proposal is the use in blocks, like: if (re.match(...) as m): print(m.groups(0)) if (input("> ") as cmd): def run_cmd(cmd=cmd): # Capture the value in the default arg print("Running command", cmd) # Works But what I like has nothing much to do with scope limitation. It is only about the idea of introducing a variable into a block. We can do that with a context manager. No, there's no cleanup of the namespace in a straightforward approach, but using e.g. `_x` as a convention for names meant to be transient is already established practice and requires no special syntax. So right now, I can do these: class bind(object): def __init__(self, *args): self.args = args def __enter__(self): return self.args[0] if len(self.args)==1 else self.args def __exit__(self, *args): pass
This would cover 98% of the cases that I would want with the proposed statement-local name bindings. I suppose I could write something much more obscure that poked into the call stack and actually deleted names from scopes in the context manager. But really I rarely care about the temporary use of a few names, especially if the same few temporary names are used repeatedly for these things. On Fri, Mar 2, 2018 at 8:04 AM, Chris Angelico <rosuav@gmail.com> wrote:
-- 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 Fri, Mar 2, 2018 at 10:44 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
The only difference from simple assignment is just visual and to be more self documenting. Basically, it just says (to me at least): "I intend to use these name within this block, but don't care about them elsewhere." It's sort of an informal scope without actual scoping rules. But of course, this is just existing Python, and anyone who wants to or doesn't is free to use or not use that style. In truth, I've thought about doing it from time to time, but never actually bothered in production code, just as a toy. -- 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 03/02/2018 08:04 AM, Chris Angelico wrote:
On Sat, Mar 3, 2018 at 1:53 AM, Rhodri James wrote:
On 02/03/18 11:43, Chris Angelico wrote:
This is the part of the PEP that I really like as well -- far more useful to me than the list-comp examples. I could even be +0.5 if the "if/while/for" compound statements were kept and the comprehensions dropped -- but not with leading dots -- I would rather do $ than . . Kind of like decorators, they shouldn't be used all that often. -- ~Ethan~

PEP 572 as it stands seems to have many problems: * Asymmetry between the first and subsequent uses of the bound value * Embedded as-clauses are subtle and hard to spot * Ever more convoluted semantics are being invented to address percieved pitfalls Alternative proposal -------------------- Getting back to the original motivating use case of intermediate values in comprehensions, to my mind there is really only one clear and obvious way to write such things: [(f(y), g(y)) for x in things where y = h(x)] Possible objections to this: * Requires a new keyword, which may break existing code. - Yes, but "where" is an unlikely choice of name, being neither a noun, verb or adjective, so it probably wouldn't break very *much* code. In return, we get something that resonates with language mathematicians have been using for centuries, and a word that can be usefully googled. * Out-of-order evaluation - Comprehensions and if-expressions already have that. Extension to other use cases ---------------------------- There are some other situations in which it could be useful to have a similar construct. 1. Name bindings local to an expression: roots = ([(-b-r)/(2*a), (-b+r)/(2*a)] where r = sqrt(b*b-4*a*c)) 2. Names bound as part of an expression and also available in a following suite: if m where m = pattern.match(text): do_something_with(m) There's a problem, though -- if "where" in an expression creates bindings local to that expression, they're not going to be visible in the rest of a comprehension or compound statement. So I'm thinking that there would actually be three distinct usages of "where": A. As a clause in a comprehension, where it creates bindings local to the comprehension and is treated on the same footing syntactically as "for" and "if" clauses. B. In an expression, surrounded by parentheses for disambiguation. Bindings are visible only within the parentheses. C. Following the expression of an "if" or "while" statement. Bindings are visible within the preceding expression and the following suite. Note that case B avoids the issue of whether expression-local bindings affect the LHS of an assignment, because in a[x] = (x * 2 where x = 17) the "x" bound by the where-clause is clearly restricted to the part in parentheses. This would be a syntax error: a[x] = x * 2 where x = 17 # illegal -- Greg

The syntax you propose is already in the Alternate syntax and there is an implementation at https://github.com/thektulu/cpython/tree/where-expr Already discussed, but no conclusion, for me I see two different proposals, the "[y for x in range(5) with y = x+1]" in comprehensions list, and the case in any expression "print (y with y = x+1)", as discussed the second case corresponds to a simple assignment so is less useful, but if the scope is local to the expression, that'd be useful : print(y with y = x+1); print(y) # NameError But I like it :) Le 3 mars 2018 02:36, "Greg Ewing" <greg.ewing@canterbury.ac.nz> a écrit :

On 3 March 2018 at 11:36, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I know Guido is on record as not wanting to allow both "for name in sequence" and "for name = expr" due to that being a very subtle distinction between iteration and simple assignment (especially given that Julia uses them as alternate spellings for the same thing), but I'm wondering if it may be worth considering such a distinction in *with statements*, such that we allowed "with name = expr" in addition to "with cm as name" (where "name = expr" is just an ordinary assignment statement that doesn't trigger the context management protocol). The related enhancement to comprehensions would then be to allow with clauses to be interleaved between the for loops and the if statements, such that you could write things like: pairs = [(f(y), g(y)) for x in things with y = h(x)] contents = [f.read() for fname in filenames with open(fname) as f] while still preserving the property where comprehensions can be correctly interpreted just by converting each of the clauses to the corresponding statement form. Even without the "with name = expr" change, allowing with clauses in comprehensions would let you do (by way of a suitably defined "bind" CM): pairs = [(f(y), g(y)) for x in things with bind(h(x)) as y] Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan writes:
pairs = [(f(y), g(y)) for x in things with y = h(x)] contents = [f.read() for fname in filenames with open(fname) as f]
This is horrible. I think Julia is just weird: in normal English we do distinguish between equality and membership. "x in y" is a very different statement from "x = y". I think even Guido would come around to the view if it were implemented (assuming not "over his dead body"). But the semantics of "x = y" and "y as x" in English are both pretty much the copula. It's hard enough to stay aware that there be dragons in a context manager; if "with" could denote a simple (local) binding, it would require conscious effort.
This is *much* better. But suppose you wanted to have *several* bindings. Would this syntax allow "destructuring" of tuples (as the for clause will do): pairs = [(f(x) + g(y), f(x) - g(y)) for w, z in pairs_of_things with bind((h(w), k(z)) as (x, y)] ? This is a question about all the proposals for local binding, I think. Steve

On 3 March 2018 at 11:36, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I'll note that a significant benefit of this approach over the PEP 572 approach is that it would be amenable to a comprehension style scope-management solution: these expressions could create an implicitly nested function and immediately call it, just as 3.x comprehensions do. Adopting such an approach would *dramatically* lower the impact that hiding the bindings from the surrounding scope would have on the overall name resolution semantics. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 3 March 2018 at 07:45, Nick Coghlan <ncoghlan@gmail.com> wrote:
While I'm *still* not sure any of this provides enough benefit to be worth doing, I will say that this proposal feels far less complicated than PEP 572. I don't particularly like the extension to if and while statements (and it's been mentioned that there are remarkably few cases where the value you want to capture is the actual condition rather than a value the condition tests) but otherwise I'm OK with it (call me -0 on the proposal without if/while, and -0.5 on the if/while part). Paul

Le 3 mars 2018 08:45, "Nick Coghlan" <ncoghlan@gmail.com> a écrit : On 3 March 2018 at 11:36, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I'll note that a significant benefit of this approach over the PEP 572 approach is that it would be amenable to a comprehension style scope-management solution: these expressions could create an implicitly nested function and immediately call it, just as 3.x comprehensions do. Adopting such an approach would *dramatically* lower the impact that hiding the bindings from the surrounding scope would have on the overall name resolution semantics. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/ As I said, allowing this syntax (whether with a new keyword like 'where' or reusing something like 'with' to write print(y with y = x+1)) in any expression (not only after a "for" in a comprehension list) is useful only if a local scope is created (otherwise, it would be the same as simple assignement but in reverse ordre). One could see : print(y with y = x+1) As a shortcut for : print(next(y for y in [ x+1 ])) The same as this : [y for x in range(5) with y = x+1] being a shortcut for : [y for x in range(5) for y in [ x+1 ]] As said, allowing both use cases would lead to two different ways to write : [y for x in range(5) with y = x+1] vs [y with y = x+1 for x in range(5)] But that is not really an issue, it's logically different (is the y a conclusion of the iteration or a hidden lambda in the expression?).

Robert Vanden Eynde wrote:
Or more straightforwardly, print((lambda y: y)(x + 1)) This is how the semantics of let-type constructs is often defined in lambda-calculus-inspired languages such as Scheme and Haskell, and it suggests a viable implementation strategy. -- Greg

On Sat, Mar 03, 2018 at 02:36:39PM +1300, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
``where`` is a very popular name in code related to SQL. Take for example ORMs: https://github.com/sqlobject/sqlobject/search?l=Python&q=where&type=&utf8=%E2%9C%93 https://github.com/zzzeek/sqlalchemy/search?l=Python&q=where&type=&utf8=%E2%9C%93
-- Greg
Oleg. -- Oleg Broytman http://phdru.name/ phd@phdru.name Programmers don't die, they just GOSUB without RETURN.

Nathan Goldbaum wrote:
Bummer, I didn't know numpy used it. That puts rather a big dampener on the idea of making it a keyword. :-( Remaining options include: * Make it a keyword only in certain contexts. That's been done before, but only as a temporary measure. Making it a permanent feature seems a bit hackish, and could cause problems for syntax highlighters. * Pick another word that isn't used often. My next choice would be "given", with maybe "letting" as a distant third. (Not just "let", because it doesn't read right after the expression.) * Re-use another keyword. Here "with" seems to be the best choice, but that would entail giving it two wildly different meanings. Guido probably isn't going to like that, since he has already expressed disapproval of doing it with "for". <tangential_ramble> Some people thought Wirth was mad when he made all the keywords in Modula upper-case, but maybe he knew a thing or two. Going back further, Algol required all keywords to be marked specially in some way (that was left up to the implementation). If I ever design another language, I think I'm going to require the source to be HTML, and insist that all keywords be <b>bold</b>. </tangential_ramble> -- Greg

On 03/02/2018 03:43 AM, Chris Angelico wrote:
Looks nice, thanks for the updates!
Alternative proposals =====================
Just to clarify: "bound in the same scope" means exactly that -- if the (EXPR as NAME) is in a comprehension, then it is local /to that comprehension/ (emphasis because I didn't adequately convey that it my initial response to Nick). Still -1 to the PEP as written. (It would be positive for alternative 6. ;) -- ~Ethan~

On 2018-03-02 03:43, Chris Angelico wrote:
After following the discussion here, I think I've come to the conclusion that something like "EXPR with NAME as VALUE" is better, and that's because the "out-of-order" execution there is actually a benefit. The example that gave me the most pause in this thread was Paul Moore's involving the quadratic formula (slightly emended here :-): x = [(-b + sqrt((b**2 - 4*a*c as D))/(2*a), (-b + sqrt(D)/(2*a)] To me this is a classic example of the kind of thing I would want to do with a statement-local binding, so whatever syntax is chosen won't be useful to me if it doesn't nicely work in situations like this. But I don't find the above example nice, for more or less the reason Paul cited: the need to assign the expression "inline" (at the point where it's first defined) creates an asymmetry that masks the fact that it's the same expression being re-used elsewhere --- and it's exactly that re-use that I would want to HIGHLIGHT with a statement-local binding. The alternative with the name binding at the end is much better: x = [(-b + sqrt(D))/(2*a), (-b - sqrt(D))/(2*a) with b**2 - 4*a*c as D] To my mind, it's a real desideratum for a statement-local binding that it should pull the bound expression OUT of the enclosing context. The overall statement gains in readability only if the reader can easily see a shared element across multiple parts. If I can't tell at a glance that the two roots both involve the same value D, there's little point in having a statement-local binding for it. (It still may gain in computational cost, since the expression won't have to be evaluated multiple times, but I see that as a much less important benefit than readability.) Also note that the version above comes perilously close to the existing solution with a regular local variable: D = b**2 - 4*a*c x = [(-b + sqrt(D))/(2*a), (-b - sqrt(D))/(2*a)] The only difference is that now D is "leaked" to following code. Nick Coghlan has argued that there's no point in an inline-assignment construct if it's not somehow local, since a big part of its purpose is to simplify reasoning by avoiding any stomping on "real" local variables. But if that's the case, maybe what we want is actually another thing that's been discussed many times on this list, namely something like a with-block that can define "super-local" variables that disappear at the end of the block: with b**2 - 4*a*c as D: x = [(-b + sqrt(D))/(2*a), (-b - sqrt(D))/(2*a)] This the same as my earlier version with "with", except the with clause comes at the beginning. There's no need to go into extreme detail here on these proposals as they're not really what's proposed by this PEP. But my point is that, from my perspective, they have something crucial that the current proposal lacks: they explicitly separate the *definition* of the shared expression from its *use* within the statement. Having to do the name-binding inline at the place where the re-used expression happens to occur makes the overall construct LESS readable for me, not more, so I'm -1 on the current proposal. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On Sat, Mar 3, 2018 at 5:30 AM, Brendan Barnwell <brenbarn@brenbarn.net> wrote:
Maybe they should be. There are two halves to this proposal: 1) A syntax for capturing an expression with a name binding 2) A scoping system for statement-local names. I'm already seeing some support for the first half without the second ("just make it a regular assignment"). Do we need two completely separate PEPs to handle these two proposals? They could link to each other saying "this could work well with that", but they have independent value. Statement-local names without a syntax for name binding would be useful in basically two contexts - the "with" syntax given here, and "except Exception as e:", which currently uses a bit of a weird hack of unbinding the existing name. But the 'with' statement would then have a messy dual form. It can be used for name bindings, and it can also be used for resource management. The easiest way to handle this is: def object.__enter__(self): return self def object.__exit__(self, *a): pass But I don't know that people want that. ChrisA

(1) I am concerned with the proposal's ability to introduce variables with a new broader kind of multi-line scope not seen anywhere else in Python. It is difficult to reason about, particularly in constructs like lambdas and inline def functions. Limiting scope to the very same line is great:
stuff = [[(f(x) as y), x/y] for x in range(5)]
However I advocate that named expressions should ONLY be usable in the header of statements that enclose other statements. Thus I would expect the following to be an error:
if (re.match(...) as m): print(m.groups(0)) # NameError: name 'm' is not defined
while (sock.read() as data): print("Received data:", data) # NameError: name 'data' is not defined
Using the named expression in other parts of the statement header is still fine:
if (re.match(...) as m) is not None and m.groups(1) == 'begin': ...
In summary, -1 from me for introducing a new kind of sublocal scope. +0 from me for keeping the scope limited to the header of the statement. Predictable conservative semantics. (2) If there WAS a desire to allow a named expression inside the substatements of a compound statement, I would advocate that the named expression just be treated as a regular (usually-)local variable assignment, with scope that extends to the entire function. This would imply that the variable would appear in locals(). Under this interpretation, the following would be okay:
I would be -0 for using a regular local scope for a named expression. Predictable semantics but leaks the expression to the wider scope of the function. (3a) With a header-limited scope (in proposal #1 above), I advocate that a named expression should NOT be able to shadow other variables, giving a SyntaxError. I can't think of a reasonable reason why such shadowing should be allowed, and preventing shadowing may eliminate unintentional errors. (3b) With a local scope (in proposal #2 above), a named expression simply replaces any preexisting local with the same name. (Or it would replace the global/nonlocal if a global/nonlocal statement was in use.) There is no shadowing per-se. Cheers, -- David Foster | Seattle, WA, USA On Fri, Mar 2, 2018 at 3:43 AM, Chris Angelico <rosuav@gmail.com> wrote:
-- David Foster

On Sat, Mar 17, 2018 at 5:49 PM, David Foster <davidfstr@gmail.com> wrote:
Header-limited scope is hard to define. Do you mean expression-local? (Also hard to define.) Do you mean local to one line of source code? Definitely not. And what happens with a 'for' loop - part of its header gets run after each loop iteration but still kinda references stuff that was done once-only before the loop started. ChrisA

I mean approximately local to one line of source code. Perhaps the unpopular opinion based on your reaction. :) More specifically, for a simple statement (with no trailing colon), there is one scope enclosing everything in the statement. For a compound statement, composed of multiple clauses, where each clause has a header (ending with a colon) and a suite, there are N non-overlapping scopes, one scope for each of the N clause headers. The scope is limited to the header only and does not include the suite. In considering a 'for' loop, I'd advocate for keeping the scope of the expression_list separate from the target_list, since I can't think of a reasonable case where the target_list would want to reference something from the expression_list. So the following code would have a NameError for magic_index in the target_list:
That's pretty bizarre code, using a fixed index of an array as an iteration variable. The only other type of 'for' loop target that might try to use a named expression from the expression_list is a slice expression, which would be even more bizarre code. Best to make bizarre cases into errors. Cheers, -- David Foster | Seattle, WA, USA On Sat, Mar 17, 2018 at 12:13 AM, Chris Angelico <rosuav@gmail.com> wrote:
-- David Foster

Disclaimer: I skimmed/searched through the PEP 572 threads (or should I say "literature"?) and did not find a discussion of the following point. If it has been discussed already, I'd be glad to be pointed to it. I am aware that Python, in contrast to C-like languages, has chosen not to treat assignments (=) as an expression with a value. The motivation for this is to avoid bugs due to confusion with the equality operator (==). But wouldn't it be a good alternative to PEP 572 to *add* an assignment operator that is an expression to Python, and is distinct from "=", for example ":="? Then, instead of PEP 572's: stuff = [[(f(x) as y), x/y] for x in range(5)] one could write stuff = [[(y := f(x)), x/y] for x in range(5)] In difference to PEP 572, the variable y would not be statement-local, which IMHO would be a welcome simplification. (PEP 572 introduces a third class of variables to Python.) Overall, it seems to me that introducing a new operator ":=" would serve the same purpose as PEP 572, but in a simpler and arguably cleaner way, while eliminating the risk of confusion with "==". The operator "=" would be left around, but could be deprecated in Python 5 and removed in Python 6. It would certainly suit a language that is widely used in education to sharpen the distinction between assignment and equality operators. Cheers, Christoph

I also think it's fair to at least reconsider adding inline assignment, with the "traditional" semantics (possibly with mandatory parentheses). This would be easier to learn and understand for people who are familiar with it from other languages (C++, Java, JavaScript). On Fri, Mar 23, 2018 at 9:34 AM, Christoph Groth <christoph@grothesque.org> wrote:
-- --Guido van Rossum (python.org/~guido)

On 03/23/2018 07:38 PM, Chris Angelico wrote:
On Sat, Mar 24, 2018 at 3:58 AM, Guido van Rossum wrote:
On Fri, Mar 23, 2018 at 9:34 AM, Christoph Groth wrote:
I'm certainly hoping PEP 572 is rejected so we can have a follow-up PEP that only deals with the assignment-as-expression portion. No offense intended, Chris! :) In fact, maybe you could write that one too, and then have an accepted PEP to your name? ;) -- ~Ethan~

On Sat, Mar 24, 2018 at 2:09 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Okay, maybe I *do* need to split them. There are other uses for statement-local names, so they're both potentially viable. Would that be the best way forward? If I do, sooner would be better than later - I'd want to grab PEP 573 while it's still available. ChrisA

On 24 March 2018 at 13:15, Chris Angelico <rosuav@gmail.com> wrote:
Inline assignment (whether using the "expr as name" syntax or a new dedicated "x := y" one) certainly comes up often enough that it would be worth writing up independently of any of the statement local scoping proposals. One benefit of " x := y" is that it would avoid ambiguity in the with statement and exception handling cases: with cm := cm_expr as enter_result: ... try: ... except exc_filter := exceptions_to_catch as caught_exc: ... In comprehensions and generator expressions, we'd need to explain why inline assignments in the outermost iterator expression leak but those in filter expressions, inner iterator expressions, and result expressions don't. It would also result in two different ways to handle traditional assignments: x = expr x := expr Perhaps ":=" could be explicitly restricted to only single names on the LHS, without any of the clever unpacking features of full assignment statements? Unlike full assignment statements, assignment expressions also wouldn't have anywhere to put a type annotation. That way, there'd technically be overlap between the two in the simple case of assigning to a single name, but in other circumstances, it would be obvious which one you needed to use. Cheers, Nick. P.S. Pascal was one of the first languages I used to write a non-trivial application (a game of Battleships), so I'm predisposed towards liking ":=" as an assignment operator :) -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 03/24/2018 01:35 AM, Nick Coghlan wrote:
In comprehensions and generator expressions, we'd need to explain why inline assignments in the outermost iterator expression leak but those in filter expressions, inner iterator expressions, and result expressions don't.
I don't understand -- could you give an example? -- ~Ethan~

On Sun, Mar 25, 2018 at 2:48 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Let's suppose we have assignment expressions. I'm going to use "(expr as name)" syntax for this example. a = [(1 as b) for c in (d as e) if (2 as f)] Which of these six names is local to the comprehension and which can leak? Due to the requirements of class scope, 'd' must be looked up in the outer scope. That means that its corresponding 'as e' must also land in the outer scope. 'a', of course, is in the outer scope. The other four are local to the inner function that implements the comprehension. ChrisA

On Sun, Mar 25, 2018 at 2:59 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
No, it's looked up inside func, and the value is found outside of func. Consider: d = 9 def func(): e = d d = 1 What's happening with a comprehension is more like: d = 9 def func(_iter): ... body of comprehension func((d as e)) which is looking it up _outside_ the function, then passing it as a parameter. ChrisA

On 03/24/2018 09:00 PM, Chris Angelico wrote:
On Sun, Mar 25, 2018 at 2:59 PM, Ethan Furman wrote:
On 03/24/2018 08:51 PM, Chris Angelico wrote:
That is invalid Python.
Looks like a buggy implementation detail. Any assignments that happen inside a listcomp should be effective only inside the listcomp. -- ~Ethan~

On 25 March 2018 at 14:08, Ethan Furman <ethan@stoneleaf.us> wrote:
Looks like a buggy implementation detail. Any assignments that happen inside a listcomp should be effective only inside the listcomp.
No, the fact that the expression defining the outermost iterable gets evaluated in the outer scope is behaviour that's explicitly tested for in the regression test suite. The language reference spells out that this is intentional for generator expressions, where it has the added benefit of reporting errors in the outermost iterable expression at the point where the genexp is defined, rather than at the point where it gets iterated over: https://docs.python.org/3/reference/expressions.html#generator-expressions Independently of the pragmatic "getting them to work sensibly at class scope" motivation, comprehensions inherit those semantics by way of the intended semantic equivalence between "[x for x in sequence]" and "list(x for x in sequence)". Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 03/24/2018 09:24 PM, Nick Coghlan wrote:
Thank you (everyone!) for your patience. Using the latest example from Angelico and myself: --> d = 9 ... def func(_iter): ... ... body of comprehension --> func((d as e)) The sticking point is the `func((d as e))`, which to my mind should happen inside the comprehension, but needs to happen outside -- and the reason it has to happen outside is so that the interpretor can verify that `d` actually exists; however, I think we can have both: --> def func(_iter): ... ... ... e = d ... ... --> d --> func() This way, the assignment does not leak, but the referenced name is still looked up and verified to exist outside the function call. I don't know how easy/difficult that would be to implement. At this point a simple confirmation that I understand and that in theory the above would solve both issues is what I'm looking for. Or, of course, the reasons why the above would not, in theory, work. -- ~Ethan~

On 28 March 2018 at 00:52, Ethan Furman <ethan@stoneleaf.us> wrote:
Just what Chris said earlier: the expression defining the outermost iterable can be arbitrarily complex, and what you're suggesting would only be feasible for a top-level name binding, not for nested ones. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 25 March 2018 at 13:51, Chris Angelico <rosuav@gmail.com> wrote:
Adding qualifiers to the names to show which names would be resolved where: outer_a = [(1 as inner_b) for inner_c in (outer_d as outer_e) if (2 as inner_f)] print(outer_a, outer_d, outer_e) # Works print (inner_b, inner_c, inner_f) # Name error In current Python,with the outer scope's name bindings being read-only inside a comprehension, this difference in scope resolution really only matters at class scope, and when using the two-namespace form of exec and eval (since method semantics result in that impacting which names the comprehension can see). At function scope, referenced names from the outer scope get resolved as closure references, while at module scope they get resolved as global references, so you have to look at the generated bytecode to see the differences. That all changes once we start allowing name binding (in any form) at the level of expressions rather than statements: regardless of the nature of the outer scope, the exact structure of the implicit comprehension scope becomes relevant whenever you bind names inside the comprehension. Now, the notable *distinction* here relative to the old iteration variable leakage is that doing a name binding in the outermost iterable expression can always be refactored to use an ordinary assignment statement instead: outer_e = outer_d outer_a = [(1 as inner_b) for inner_c in outer_d if (2 as inner_f)] print(outer_a, outer_d, outer_e) # Works print (inner_b, inner_c, inner_f) # Name error This formulation also makes it clear that "outer_e" will only resolve inside the comprehension if "outer_d" already resolves. This means that if we did go down the expression-level-assignments-are-just-regular-name-binding-operations path, then using the embedded assignment form in such cases could be discouraged as misleadingly ambiguous by style guides and linters, without being outright prohibited by the compiler itself. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
Why would "=" have to be kept for anything else but backwards compatibility and saving keystrokes? The expression var: annotation := value could assign a value to a name and it would have the corresponding value. I guess that it's problematic for the language grammar to allow things like for i in (var: annotation := value): print(i) In that case such constructions could be declared illegal.
Having an assignment operator that is optically distinct from '=' is IMO a strong point of Pascal. For Python, an even better assignment operator would be IMO "<-", but this is not possible without breaking backwards compatibility, and also it's a bit difficult to type on an English keyboard.

Chris Angelico wrote:
Thank you; both of these have now been incorporated into the document.
Thanks! Just a small comment. You wrote in the PEP draft:
I don't understand what you mean here. If the (y as x) syntax is to have, as you say, "the exact same semantics as regular assignment", then assignments inside list comprehensions would always "leak". But this is a good thing, because this is consistent with how Python behaves. Christoph

On Sat, Mar 24, 2018 at 8:07 PM, Christoph Groth <christoph@grothesque.org> wrote:
Except that a list comprehension is implemented using an inner function. Very approximately: x = [n * m for n in range(4) for m in range(5)] def <listcomp>(iter): ret = [] for n in iter: for m in range(5): ret.append(n * m) return ret x = <listcomp>(iter(range(4)) So the first (outermost) iterable is actually evaluated in the caller's scope, but everything else is inside a subscope. Thus an assignment inside that first iterable WILL leak into the surrounding scope; but anywhere else, it won't. ChrisA

On 24 March 2018 at 09:18, Chris Angelico <rosuav@gmail.com> wrote:
Wow, that's subtle (in a bad way!). I'd much rather that assignments don't leak at all - that seems to me to be the only correct design, although I understand that implementation practicalities mean it's hard to do. There's a lot of context snipped here. Is this about the variant that just does assignment without the new scope? If it is, then is there a similar issue with the actual proposal, or is that immune to this problem (I suspect that it's not immune, although the details may differ). Paul

On Sat, Mar 24, 2018 at 10:49 PM, Paul Moore <p.f.moore@gmail.com> wrote:
The code equivalence I gave above applies to the existing Python semantics. I'm not sure why the outermost iterable is evaluated in its enclosing context, but there must have been a good reason. ChrisA

On 24 March 2018 at 11:55, Chris Angelico <rosuav@gmail.com> wrote:
Understood. But the current Python semantics doesn't have any way of binding a name as part of that outermost iterable. It's the interaction of the new feature (name binding in expressions) with the existing implementation (the first iterable is outside the constructed scope for the comprehension) that needs to be clarified and if necessary modified to avoid nasty consequences. Paul

On 24 March 2018 at 21:49, Paul Moore <p.f.moore@gmail.com> wrote:
We can't do that because it would make comprehensions nigh-unusable at class scope (or, equivalently, when using exec with a separate locals namespace): class C: _sequence = "a b c d".split() _another_sequence = [f(item) for item in _sequence] "_sequence" needs to be resolved in the class scope and passed in as an argument in order for that to work, as the nested scope can't see it directly (since the implicit nested scope works like any other method definition for name resolution purposes).
PEP 572 is *mostly* immune, in that it's only the rest of the same statement that can see the name binding. The variant that *doesn't* introduce statement local scoping just leaks outright into the surrounding scope. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Chris Angelico wrote:
Indeed, the Python language reference section 6.2.4 says: "Note that the comprehension is executed in a separate scope, so names assigned to in the target list don’t “leak” into the enclosing scope." As far as I can tell this has only consequences for the loop index variables of the comprehension, because these are the only names that are assigned values inside the comprehension. After all, assignment is currently a statement in Python, so it's not possible inside a list comprehension to assign to other names. (Or am I overlooking something here?)
It seems to me that this is a CPython implementation detail. Couldn't x = [n * m for n in range(4) for m in range(5)] be equally well equivalent to def <listcomp>(): ret = [] for n in range(4): for m in range(5): ret.append(n * m) return ret x = <listcomp>() As far as I can tell, with today's Python both implementations (yours and mine) are indistinguishable by the user. Since Python 3 decided to put comprehensions in a scope of their own, that means that if an assignment operator ":=" is added to Python the list comprehension [(t := i**2, t**2) for i in (r := range(10))] should neither leak r, nor t nor i. Seems fine to me! Christoph

On 24 March 2018 at 23:29, Christoph Groth <christoph@grothesque.org> wrote:
They can be distinguished, just not at module or function scope. To give a concrete example: ========== >>> class C: ... sequence = range(10) ... listcomp = [x for x in sequence] ... def works(data): ... return list(data) ... from_works = works(sequence) ... def fails(): ... return list(sequence) ... >>> C.listcomp [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> C.from_works [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> C.fails() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 8, in fails NameError: name 'sequence' is not defined ========== I think I did have an implementation that didn't do the "outermost iterator is evaluated in the outer scope" dance early on (back when I was still working on the initial version of the iteration variable hiding, before it was merged to the Py3k branch), but quickly changed it because relying solely on closures broke too much code (in addition to the class namespace case, you can create a situation with similar constraints by passing a separate locals namespace to exec). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

2018-03-24 17:14 GMT+03:00 Nick Coghlan <ncoghlan@gmail.com>:
But is the example with the class appropriate in this context, it seems that it is well understood why this example will fail, and `C.fails()` should at least be a `@classmethod`:) From the docs:
And if we cross out class, exec and eval cases - they don't work now, they will not be affected in future. Are not these examples equivalent? With kind regards, -gdg

On 25 March 2018 at 01:03, Kirill Balunov <kirillbalunov@gmail.com> wrote:
I don't follow what you're trying to say here. Comprehensions *do* work with class bodies, two-namespace exec, and two-namespace eval, and one of the consequences of the *way* they work that proposals for expression level name binding will all have to deal with is that the outermost iterable expression is evaluated in the containing scope rather than the implicitly nested one. Statement local name bindings would restrict any resulting name leakage from the outermost iterable to the affected statement, while regular name bindings would affect the entirety of the containing scope. The latter behavour is of the larger downsides of having expression level name bindings work the same way assignment statements do, since it recreates a variant of the original iterator variable name leakage that the introduction of the implicitly nested scope eliminated. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 25 March 2018 at 02:34, Eric Fahlgren <ericfahlgren@gmail.com> wrote:
Everything except the outermost iterator is evaluated in the implicitly nested scope, so comprehensions at class scope have the restriction that only the outermost iterator can access names defined in the class scope. It turned out that was enough to preserve compatibility with most of the comprehensions that folks actually use at class scope. For those rare cares where it isn't, the typical resolution is to either define a helper function, or else switch to a regular for loop. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sat, Mar 24, 2018 at 7:08 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Just in case anyone wonders, I don't think the special rules around class scope count are a wonderful feature. 28 years ago it was the best we could do, and by the time we realized its problems -- all rare edge cases for sure, but unintuitive and hard to debug when they strike -- we were tied by backward compatibility. And we still are. (At the time there were no nested scopes, there was just local, global, builtins; class scope was treated as a function scope and that was that.) (I have a lot more to say about PEP 572 but I'll do it in the "take three" thread.) -- --Guido van Rossum (python.org/~guido)

Nick Coghlan wrote:
Thanks a lot for this example. So you mean that while listcomp = [x for x in sequence] works above listcomp = [(y,x) for y in sequence for x in sequence] doesn't (but it would if the class was replaced by a function). That's indeed a bit strange, and I would consider it somewhat of a wart of the language. But as far as I can tell remaining compatible with the above behavior does not force us to leak assignments from the outermost scope of a comprehension. I.e. there's nothing in the language currently that forces listcomp = [x for x in (r := sequence)] to leak the name "r". Granted, it's a bit strange if in the above line the name "sequence" is evaluated in class scope but the name "r" is set in the comprehension scope, but since currently there is no way to assign values to names in comprehensions this "hybrid" behavior would be backwards-compatible, and less surprising than leaking "r". This could be fixed if backwards compatbility may be ever broken again, but until then I don't expect it to be a problem.

On Sun, Mar 25, 2018 at 8:31 PM, Christoph Groth <christoph@grothesque.org> wrote:
It seems fine in a simple example, but remember, an assignment expression can be used ANYWHERE in an expression. Consider: listcomp = [x for x in obj[r := f()][x + r] ] Which parts happen in the inner scope and which in the outer scope? If 'r' is created in a subscope, it has to be a subscope of the outer scope; if it's not subscoped, it has to be directly in the outer scope. It can't sanely be in the inner scope. ChrisA

Chris Angelico wrote:
The example you give is indeed difficult to understand, but all programming languages, even Python, allow to write confusing code. Already today we can have listcomp = [x for x in obj[f()][x + r]] and, while valid, that's hardly an example worthy of emulation. Compared to this, the example that you give is indeed even more treacherous, because innocent people could assume that the 'r' in '[x + r]' is the one set just before with ':='. But is this obscure behavior really a problem? A bug can only happen if 'r' is also defined in the surrounding scope, otherwise there will be an error. And if this is indeed considered a problem, such confusing situations could be detected and flagged as an error by the Python compiler until the underlying inconsistency in the language is fixed. I think that it's a helpful guideline to imagine what the ideal behavior should be if we were not constrained by backwards compatibility, and then try to follow it. In the case at hand, we all seem to agree that the fact that the outermost iterator of a comprehension is evaluated in the surrounding scope is somewhat of a wart, although one that is rarely visible. The existence of a wart shouldn't pull us further into the wrong direction.

On 25 March 2018 at 22:44, Christoph Groth <christoph@grothesque.org> wrote:
There's no such agreement, since generator expressions have worked that way since they were first introduced almost 15 years ago: https://www.python.org/dev/peps/pep-0289/#the-details It's much easier to see the difference with generator expressions, since the evaluation of the loop body is delayed until the generator is iterated over, while the evaluation of the outermost iterator is immediate (so that any exceptions are more easily traced to the code responsible for them, and so that they don't need to create a closure in the typical case). With comprehensions, the entire nested scope gets evaluated eagerly, so it's harder to detect that there's a difference in the evaluation scope of the outermost iterator in normal use. That difference *does* exist though, and we're not going to tie ourselves into knots to try to hide it (since the exact same discrepancy will necessarily exist for generator expressions, and semantic consistency between genexps and the corresponding comprehensions is highly desirable). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
I was referring to [1], but I see now that things are more subtle. Guido didn't express a dislike for the special treatment of the outermost iterator, just (if I understand correctly) for the consequences of this in class scope. [1] https://mail.python.org/pipermail/python-ideas/2018-March/049461.html
Thanks a lot for the explaination. I know many of the more obscure aspects of Python, but I wasn't aware of this. I'm still not sure whether I agree that it is a good idea to treat the evaluation of the outermost iterator differently from the rest. It can be especially confusing that the outermost iterator appears in the middle of the generator expression, surrounded by code that is evaluated in the inner scope. But I don't want to start a futile discussion about backwards-incompatible changes here.
With tying into knots you mean my suggestion to bind both 'a' and 'b' at the internal scope in gen = (i+j for i in x[a := aa] for j in (b := bb)) even if x[a := aa] is evaluated in the outer scope? I tend to agree that this is indeed too arcane. But then how about making ":=" work always at the outer scope (and thus reserve the inner scope of generators only for the index variables), i.e. make the above equivalent to: def g(it): nonlocal b for i in it: for j in (b := bb): yield i+j gen = g(iter(x[a := aa])) That would be less confusing and indeed it could even turn out useful to be able to access names that were bound inside the generator outside of it. Note that the use of ":=" inside generator expressions would be optional, so it can be considered fair to treat such assignments differently from the implicit assignments to the loop variables.

FWIW, I thought another way which provides cache object library, it seems to just work in some cases. But it doesn't create statement local scope and might be difficult to read because looks ordinary expression doing magic. Chris, would you append the library to alternative proposal section? class Cache: """ Return a object which stores a value called with a keyword and immediately return it, then get the value using the attribute. """ def __call__(self, **kwargs): (k, v), = kwargs.items() # require one keyword setattr(self, k, v) return v c = Cache() print(c(spam="ham")) # => ham print(c.spam) # => ham L = [c(y=f(x)) + g(c.y) for x in range(5)] num = int(c.s) if c(s=eggs()).isdigit() else 0 if c(match=search(string)) is not None: print(c.match.groups()) while c(command=input()) != "quit": print("command is", c.command) -- Masayuki 2018-03-02 20:43 GMT+09:00 Chris Angelico <rosuav@gmail.com>:
participants (21)
-
Brendan Barnwell
-
Chris Angelico
-
Christoph Groth
-
David Foster
-
David Mertz
-
Eric Fahlgren
-
Ethan Furman
-
Greg Ewing
-
Guido van Rossum
-
Jelle Zijlstra
-
Jonathan Goble
-
Kirill Balunov
-
Masayuki YAMAMOTO
-
Nathan Goldbaum
-
Nick Coghlan
-
Oleg Broytman
-
Paul Moore
-
Rhodri James
-
Robert Vanden Eynde
-
Soni L.
-
Stephen J. Turnbull