The PEP 572 endgame
Over the last few months, it became clear that the battle over PEP 572 would be consequential; its scale and vehemence was largely unprecedented in the history of Python. The announcement by Guido van Rossum that he was stepping down from his role as benevolent dictator for life (BDFL), due in part to that battle, underscored the importance of it. While the Python project charts its course in the wake of his resignation, it makes sense to catch up on where things stand with this contentious PEP that has now been accepted for Python 3.8.
We first looked at the discussion around PEP 572 back in March, when the second version of the PEP was posted to the python-ideas mailing list. The idea is to allow variable assignment inline, so that certain constructs can be written more easily. That way, an if or while, for example, could have a variable assignment in the statement and the value of the variable could be used elsewhere in the block (and, perhaps, beyond). The scope of those assignments is one of the areas that has evolved most since the PEP was first introduced.
Discussing PEPs
Even early on, some chunk of the discussion was really a "meta-discussion" about the medium for debating PEPs—whether a mailing list is truly better than, say, a forum of some kind. That debate somewhat foreshadowed later developments, as there are now various thoughts on changing the PEP-discussion process to make it so that people can follow the arguments and chime in with their own without having to commit to constant monitoring of the mailing list posts. As seen with this PEP, those posts are often redundant or repetitive, evince no familiarity with either the PEP or the preceding discussion, and generally are simply a waste of participants time—but, of course, not all of them are.
Discussions on python-ideas are meant to be more freewheeling but to eventually converge on something concrete and potentially agreeable before moving to python-dev. It has been hoped that the python-dev discussions will be more focused and less rancorous, but that clearly did not play out for PEP 572. Van Rossum brought up the "PEP 572 mess" in a session at the 2018 Python Language Summit; he wanted to explore other ways to discuss PEPs as part of that session. Shortly after that session, he posted an idea about better PEP discussions to the python-committers mailing list, where only core developers can post. He suggested moving PEP discussions to GitHub repositories (or those of another online Git provider):
There were some questions about the suitability of GitHub for PEP
discussion, but most seemed inclined to try it and see how it worked out. Mark
Shannon did try the idea out for PEP 576, but it did not really work, at
least in that case, as it "didn't reduce the amount of email traffic on
python-dev
".
Meanwhile back at the PEP itself, much of the complexity was removed or filed down and the title changed from "Syntax for Statement-Local Name Bindings" to "Assignment Expressions". The underlying motivation was much the same, however. PEP 572 allows for assigning values to variables in places where only expressions are allowed. The ":=" operator was chosen (among a wide array of alternative spellings) to indicate that, which could be read as "becomes". One of the canonical examples of its use is to replace a so-called "loop and a half":
line = f.readline() while line: ... # process line line = f.readline()or
while True: line = f.readline() if not line: break ... # process linecould be replaced with:
while line := f.readline(): ... # process line
The main argument for the feature is that it is more readable and makes the
programmer's intent clearer. But most of the traffic in the threads,
from core developers and others, was against adding it. Even the original
PEP author,
Chris Angelico, was not wildly in favor
("hey, I'm no more than +0.5 on it myself
"), at least in the
early going.
While the PEP was still being discussed on python-ideas, Van Rossum got more involved and Tim Peters posted some of his own code that he thought could benefit from assignment expressions. As the "Rationale" section of the PEP notes, "toy" examples are not necessarily useful in trying to evaluate a language feature:
That section also describes some code that Van Rossum found in the Dropbox code base, where it was clear that programmers valued conciseness even to the point of repeating a potentially expensive operation to express it on a single line. In addition, an essay from Peters giving some real world examples is Appendix A of the PEP. While it was still not a foregone conclusion that Van Rossum would accept the PEP, his interest and participation seemed to point that way.
To python-dev
After "four rounds in the boxing ring at python-ideas
",
Angelico posted the PEP to the more widely
read python-dev mailing list in mid-April. That set off a firestorm of
replies, alternatives, a survey of how other languages handle this feature
(if at
all), appeals to PEP 20 ("The Zen of
Python"), and so on. As Van Rossum said in his Language Summit talk, the
opposition may have picked up once people realized he was seriously
considering accepting the PEP.
Angelico seemed less confident of acceptance. In his second posting of the PEP to python-dev, he
said: "So here's the PEP again, simplified. I'm fairly sure it's just
going
to be another on a growing list of rejected PEPs to my name
". That
posting predates much of the firestorm, however. As it turns out, he was
a bit premature with his pessimism.
Many of the objections (those that were not bikeshedding over the syntax anyway) seem to revolve around the idea that PEP 572 would add a new way to assign to a variable that would be confusing to new programmers. Several core developers were concerned about how to teach the new operator and how to distinguish it from the "normal" assignments using =. Van Rossum admitted the benefits of the feature are "moderate", so it perhaps should not be a huge surprise that there was not wild acclaim for it. In fact, an informal poll of core developers right after the summit found few in favor of the change.
Another huge thread was spawned from
Antoine Pitrou's thoughts on the LWN
writeup of the summit session on PEP 572. It went on in much the same vein as
many of the other discussions, though there are real efforts to improve the
PEP in the thread instead of simply opposing it or rehashing syntax questions
that had been long resolved. On July 2, however, Van Rossum posted his decision in the middle of the
thread: "Thank you all. I will accept the
PEP as is.
" As much as anything, it may have been his way of simply
ending the interminable arguments to hopefully focus on tightening up the
PEP's language and to fix any corner cases.
To a large extent, that's what most of the core developers still participating started doing. For example, Steve Dower started looking more closely at the "Syntax and Semantics" section of the PEP, with an eye toward clarifying the language (and his understanding). Victor Stinner started a thread to discuss possible places to use the feature in the standard library—with an eye toward which would actually be helpful to make the code clearer.
There were, naturally, a few last-gasp attempts to head off PEP 572, but Van Rossum was having none of that. He and Peters had joined with Angelico as authors of the PEP in mid-May and multiple changes were made, though they were not posted to the list until Van Rossum's more formal "intention to accept" was posted on July 9. In it, he asked that replies and changes stick to making the PEP better:
Acceptance ... and resignation
He then accepted the PEP on July 11;
the next morning he posted his resignation as BDFL, saying: "Now that
PEP 572 is done, I don't ever want to have to fight so hard for a
PEP and find that so many people despise my decisions.
" Obviously
the whole experience was painful and frustrating for him, which is truly
sad. It is clear that many were loudly and sometimes annoyingly opposed to
the feature, and some seriously
unhappy that it was accepted, but "despise" seems rather strong—that sounds
like frustration talking as much as anything else.
So Python will "soon" have assignment expressions, where soon means in
Python 3.8, currently scheduled for October
2019. Whatever else can be said of the feature, it has been hashed out
over many months, which likely helped eliminate any major issues with it.
It is spelled a bit weirdly for Python, as the language has generally
eschewed multi-character operators where it can, but that was a conscious
choice. Van Rossum tried to suggest using
= early on, which would be
syntactically possible, but "the negative reaction to that version
was way stronger
". Even the biggest opponent of := can
likely see uses for it even if they may avoid it on principle.
The consequences of the PEP and the battle are likely to be with the project for a long time to come. Obviously, losing Van Rossum as the final arbiter of all things Python is a big blow, but the PEP-discussion process is also going to change. It is truly the end of an era for the language, but it is also the start of a new era—Python seems likely to survive and probably thrive once it gets its feet back under it.
Index entries for this article | |
---|---|
Python | Development model |
Python | PEP 572 |
Posted Jul 18, 2018 16:40 UTC (Wed)
by augustz (guest, #37348)
[Link] (17 responses)
This one though - is a huh?
I can do y:=f(x) in an comprehension, but can't do y := f(x) elsewhere?
Moving to this syntax moves python more towards line-noise not away from it. Are people reading the examples and going - that's easier to read? I find I have to read lines with these expressions both forwards and then backwards to understand what is going on.
I guess I'm just trying to understand why this was such a critical addition given the mixed views.
Posted Jul 18, 2018 17:49 UTC (Wed)
by smurf (subscriber, #17840)
[Link] (1 responses)
Sure you can use it elsewhere. You can do it inside an if/while statement. In fact you can use it anywhere you can use an expression, which is kindof the point.
Posted Jul 26, 2018 9:04 UTC (Thu)
by t-v (guest, #112111)
[Link]
Posted Jul 19, 2018 15:47 UTC (Thu)
by rweikusat2 (subscriber, #117920)
[Link] (11 responses)
The sensible use of such a feature is usually in loop conditions, eg, looping over the lines of a file while doing some processing for each line. There'll be some sort of "step function" with side effects, eg, reading a line from the file, whose return value will either be the line or some kind of "no more lines" indicator. This can be written without assignment expressions but only in fairly awkard ways, eg, using a dummy loop condition which always evaluates to true and terminate the loop via explicit control flow control statements ('break') or (not available in Python) by using a do-while loop combined with an if and with the 'read next line' statement once in front of the if and repeated in front of the trailing while.
Posted Jul 19, 2018 18:15 UTC (Thu)
by augustz (guest, #37348)
[Link] (10 responses)
"The sensible use of such a feature is usually in loop conditions, eg, looping over the lines of a file while doing some processing for each line. There'll be some sort of "step function" with side effects, eg, reading a line from the file, whose return value will either be the line or some kind of "no more lines" indicator. This can be written without assignment expressions but only in fairly awkard ways, "
I normally do
So an existing way of doing things seems clearer (perhaps at the cost of one or two extra vertical lines) to me.
The above pseudo code is basically English, and actually runs. I can explain this program as meaning, for every line in file, print the line. It's literally what I love about python (less line noise). Given python took the pain of whitespace to avoid line noise, it feels weird to be adding some back in. I suppose in a bit of time it'll become more clearly a good addition.
Posted Jul 19, 2018 19:13 UTC (Thu)
by rweikusat2 (subscriber, #117920)
[Link] (9 responses)
Posted Jul 20, 2018 15:02 UTC (Fri)
by augustz (guest, #37348)
[Link] (8 responses)
We are talking about iterators? These have been an accepted standard in python since something like 2002. Python has done OK with this syntactical special case. I'm not against switching over to assignment expressions, but it goes to my original comment, the benefit seems modest in terms of readability etc, and the examples given seem written in weirdly complex ways relative to existing syntax.
Posted Jul 22, 2018 20:11 UTC (Sun)
by rweikusat2 (subscriber, #117920)
[Link] (7 responses)
Posted Jul 23, 2018 17:03 UTC (Mon)
by anselm (subscriber, #2796)
[Link] (2 responses)
Doing it this way is arguably preferable to the approach C takes for reading a file character by character, which is prone to subtle bugs and portability problems, mostly because people erroneously declare the loop variable as char when the return value of getchar() is int (because it must be able to represent any char value and additionally EOF).
If you are familiar with Perl's <> operator, which is basically “getchar() for lines” in that it tries to return either a valid line from a file or a “no more lines” indication, you'll know that that is not without its little gotchas, either, so trying to do without the “end of input” flag as a possible return value, as Python does in the iterator approach to reading lines from a file, is probably just as well.
Personally as a mostly-Python programmer I could live without the := operator. As far as I'm concerned it's certainly not worth the huge fuss that seems to have been made over it.
Posted Jul 23, 2018 18:03 UTC (Mon)
by excors (subscriber, #95769)
[Link] (1 responses)
A fun issue is that the string "0" is false in Perl (though "0\n" is true), so if you're reading a file like "3\n2\n1\n0", code like "while (my $line = <$f>) { ... }" would exit the loop too early and miss the last line. But thanks to Perl's DWIM magic, that loop gets executed as if it was "while (defined(my $line = <$f>)) { ... }" so it will continue until EOF.
Python doesn't have that problem because the only false string is "", and you can't have a line-based file that ends with an empty string. But you might need to be careful to do "while (line := f.read()) is not None" if whiling over some other data source that can return empty strings, which is uglier and slightly more error-prone than "for line in f".
Posted Jul 27, 2018 17:36 UTC (Fri)
by flussence (guest, #85566)
[Link]
Posted Jul 24, 2018 15:09 UTC (Tue)
by spaetz (guest, #32870)
[Link]
Pfewww, and that makes me very happy. Seriously, how many subtle ways to shoot yourself in the foot does the above line provide?
Posted Jul 25, 2018 9:49 UTC (Wed)
by jwilk (subscriber, #63328)
[Link] (2 responses)
Posted Jul 25, 2018 12:55 UTC (Wed)
by rweikusat2 (subscriber, #117920)
[Link] (1 responses)
It's still irrelevant.
Posted Jul 25, 2018 13:14 UTC (Wed)
by rahulsundaram (subscriber, #21946)
[Link]
Why is it irrelevant?
Posted Jul 21, 2018 20:44 UTC (Sat)
by k8to (guest, #15413)
[Link]
This feels entirely like "let's add a feature from other languages to python" without really worrying about whether it's needed.
Sure, some code can be more concise, but I've always, in every language, found assignments during loop conditions to be a misfeature. It's too much going on at once and special surprises can be embedded easily. In 20 years python, this feature-gap has cost me a handful of seconds a handful of times, and it's saved me hours many times. I really have no idea why after so much time the decision came down to add them in.
Posted Jul 20, 2019 17:21 UTC (Sat)
by lkcl (guest, #60496)
[Link] (1 responses)
i have called them list *IN*-comprehensions for this reason, for a long time.
the issue is not just related to humans: it also increases the LALR parser's complexity by requiring that the "comprehension" - the context - be DELAYED, pending reading of additional "tokens".
x = [x.... pause.... (oh, x = x? a list of one item then? wait.. wtf???)... forxinsomegarbage]
and that's a total "NO".
python prior to list incomprehensions was readable in a near-100% FORWARD direction. you did *NOT* mentally have to go forwards then backwards again. this *was* one its greatest strengths: ease of readability.
all they had to do to fix this was make map, filter and reduce become object-orientated members of list, dict, set and iterators in general.
the only reason that map, filter and reduce were not added that way originally was because they were proposed by a *lisp* programmer (and a patch came with the proposal) back some time around python 1.5.
i am half-torn on this one [:=]. i tend to write quite a lot of "while True:" code as well as code that has the copy-of-the-thing-that-needs-testing, just like in the example.
i do *not* think that ":=" as a new operator is a good idea. "while x = readline():" is pretty obvious, that x gets assigned and then goes into the "while" as the condition to be tested. that's how it works in c.
that having been said: continuing to "improve" a language just for improvements' sake... this rings alarm bells. improvements in the *efficiency* - by getting rid of the GIL - those *are* valuable things to focus on.
Posted Jul 23, 2019 15:07 UTC (Tue)
by mathstuf (subscriber, #69389)
[Link]
Posted Jul 18, 2018 19:21 UTC (Wed)
by dunlapg (guest, #57764)
[Link] (3 responses)
In this kind of a discussion, I almost wonder whether it wouldn't be better, after the initial discussion, to select 7 people at random from the interested parties, have them argue about it (maybe in person) for a bit, and then vote on it. Sometimes making any reasonable decision is better than an interminable discussions.
Alternately, someone could have summarized the major proposals and done a "five-point survey", where people rate each one "terrible", "not happy but wouldn't argue against", "happy but wouldn't argue for", "great", or "no opinion". That at least gets an idea how the community as a whole feels about the idea, without each individual person needing to respond via an email thread.
Posted Jul 18, 2018 19:44 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
Posted Jul 18, 2018 19:50 UTC (Wed)
by dunlapg (guest, #57764)
[Link]
Posted Jul 20, 2018 0:05 UTC (Fri)
by gerdesj (subscriber, #5446)
[Link]
a <
becomes:
a
Obvs: the < > example above is only one available layout and subject to its own religious bikeshedding. No-one knows which is the best layout of < >, it may even involve these things instead: { } but surely that way lies curly madness. The white space idea is at least uniform, provided we severely restrict our definition of "whitespace" to space and not include say tab (or can we?) or any other non printing char. that a human would see as nothing but mean something to the machine (eg BEL).
Perhaps what we need is someone or some org to make some unilateral decisions about something, set out their stall and see what happens. If it is crap it will die, if it is good enough it may thrive, if it is Python it will continue to run (on) an appreciable proportion of the world's IT systems and make the place a little bit better (except where it doesn't.)
Posted Jul 20, 2018 1:41 UTC (Fri)
by OrbatuThyanD (guest, #114326)
[Link] (1 responses)
Posted Jul 23, 2018 22:39 UTC (Mon)
by vstinner (subscriber, #42675)
[Link]
Posted Jul 24, 2018 20:47 UTC (Tue)
by acarno (subscriber, #123476)
[Link]
[1] https://redditblog.com/2009/10/15/reddits-new-comment-sor...
The PEP 572 endgame
The PEP 572 endgame
Personally, I'm surprised that they don't seem to have a "prefix-block" that would execute if the next block-starting statement is executed in the rejected alternatives. Like
The PEP 572 endgame
Not sure that that is good enough, but it seems to solve the forever-nested-else and the before-and-in-while cases, but it would seem to get rid of all corner cases, too.
I don't think I would expect that to be adopted, but I would have thought it is an alternative credible enough to be to considered and rejected.
reductor = dispatch_table.get(cls)
if reductor:
rv = reductor(x)
using:
reductor = getattr(x, "__reduce_ex__", None)
elif reductor:
rv = reductor(4)
using:
reductor = getattr(x, "__reduce__", None)
elif reductor:
rv = reductor()
else:
raise Error("un(shallow)copyable object of type %s" % cls)
The PEP 572 endgame
The PEP 572 endgame
for line in file:
print(line)
The PEP 572 endgame
The PEP 572 endgame
I was specifically referring to the idea to regard a 'file' as 'an iterable sequence of lines' so that one can "loop over a file" provided it's a text file and one desires to do that. I don't use Python (it belongs to the "will learn this once I have to handle code written in it" class of languages), hence, I'm not familiar with its features.
The benefits aren't so much in terms of readbility but in being able to express certain, not uncommon constructs in a straight-forward way. The most common one (most common in code written by me at least) is a while-loop with some sort of 'step function' which either returns the next value the loop has to work with or an indication that there are no more values. Due to lack of a do-while loop, these simply cannot be written in Python (sans assignment expressions) without abuse of flow-control constructs.
Something like this:
The PEP 572 endgame
#include <stdio.h>
int main(void)
{
unsigned lines;
int c;
lines = 0;
while ((c = getchar()) != EOF) lines += c == '\n';
printf("%u\n", lines);
return 0;
}
doesn't have a direct equivalent in Python.
The PEP 572 endgame
I was specifically referring to the idea to regard a 'file' as 'an iterable sequence of lines' so that one can "loop over a file" provided it's a text file and one desires to do that.
with open("foo") as file:
for line in file:
# Do stuff with the line
has worked in Python for quite some time. (Open text files are iterators.)
The PEP 572 endgame
The PEP 572 endgame
The PEP 572 endgame
doesn't have a direct equivalent in Python.
The PEP 572 endgame
import functools
import sys
lines = 0
getchar = functools.partial(sys.stdin.read, 1)
for c in iter(getchar, ''):
lines += c == '\n'
print(lines)
The PEP 572 endgame
The PEP 572 endgame
The PEP 572 endgame
The PEP 572 endgame
> and then backwards to understand what is going on.
The PEP 572 endgame
The PEP 572 endgame
The PEP 572 endgame
The PEP 572 endgame
The PEP 572 endgame
... stuff ...
>
b <
... more stuff ...
>
... stuff ...
b
... more stuff ...
The PEP 572 endgame
The PEP 572 endgame
The PEP 572 endgame