|
|
Subscribe / Log in / New account

The PEP 572 endgame

By Jake Edge
July 18, 2018

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):

This way the discussion is still public: when the PEP-specific repo is created the author(s) can notify python-ideas, and when they are closer to submitting they can notify python-dev, but the discussion doesn't attract uninformed outsiders as much as python-{dev,ideas} discussions do, and it's much easier for outsiders who want to learn more about the proposal to find all relevant discussion.

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 line
could 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:

However, in order to be compelling, examples should be rooted in real code, i.e. code that was written without any thought of this PEP, as part of a useful application, however large or small. Tim Peters has been extremely helpful by going over his own personal code repository and picking examples of code he had written that (in his view) would have been clearer if rewritten with (sparing) use of assignment expressions. His conclusion: the current proposal would have allowed a modest but clear improvement in quite a few bits of code.

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:

I know it is inevitable that there will be replies attempting to convince us that a different syntax is better or that we shouldn't do this at all. Please save your breath. [I've] seen every possible such response before, and they haven't convinced me. At this point we're just looking for feedback on the text of the document, e.g. pointing out ambiguities or requests for more clarity in some part of the spec, *not* for alternative designs or passionate appeals to PEP 20.

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
PythonDevelopment model
PythonPEP 572


to post comments

The PEP 572 endgame

Posted Jul 18, 2018 16:40 UTC (Wed) by augustz (guest, #37348) [Link] (17 responses)

My response to accepted PEP's has always been - that's great! Or of course that is better. Or I read it and learn something.

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.

The PEP 572 endgame

Posted Jul 18, 2018 17:49 UTC (Wed) by smurf (subscriber, #17840) [Link] (1 responses)

It's not critical by any means. It just fixes two or three annoyances which, well, are not so bad, but being able to do the requisite assignment inline is still an sufficient improvement IMHO (and in th ex-BDFL's HO) to accept a slightly-line-noise-y inline-assignment operator.

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.

The PEP 572 endgame

Posted Jul 26, 2018 9:04 UTC (Thu) by t-v (guest, #112111) [Link]

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
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)

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.

The PEP 572 endgame

Posted Jul 19, 2018 15:47 UTC (Thu) by rweikusat2 (subscriber, #117920) [Link] (11 responses)

Most of the examples from the PEP (I read) aren't really good as they're about creating if-expressions with side-effects for the purpose of "more packed/ efficient use of vertical space in a text editor". I don't think "playing for lines" in this way improves code anyhow (there are rare cases when this is technially necessary, though).

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.

The PEP 572 endgame

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

for line in file:
   print(line)

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.

The PEP 572 endgame

Posted Jul 19, 2018 19:13 UTC (Thu) by rweikusat2 (subscriber, #117920) [Link] (9 responses)

I described an abstract situation which would benefit from assignment expression: Reading a file line-by-line in a while loop based on a function which returns either the next line or some end-of-file indicator. None of this appears in your example. Hence, I don't understand what it's supposed to demonstrate here. I could argue that you're actually proving my point: Judging from a Python 3.7 tutorial, line-by-line processing of a text file is so awkward without assignment expresssions that the language has a syntactial special cause just to work around this.

The PEP 572 endgame

Posted Jul 20, 2018 15:02 UTC (Fri) by augustz (guest, #37348) [Link] (8 responses)

"line-by-line processing of a text file is so awkward without assignment expresssions that the language has a syntactial special cause just to work around this."

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.

The PEP 572 endgame

Posted Jul 22, 2018 20:11 UTC (Sun) by rweikusat2 (subscriber, #117920) [Link] (7 responses)

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:

#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

Posted Jul 23, 2018 17:03 UTC (Mon) by anselm (subscriber, #2796) [Link] (2 responses)

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.)

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.

The PEP 572 endgame

Posted Jul 23, 2018 18:03 UTC (Mon) by excors (subscriber, #95769) [Link] (1 responses)

> 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

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".

The PEP 572 endgame

Posted Jul 27, 2018 17:36 UTC (Fri) by flussence (guest, #85566) [Link]

That's a nasty-sounding edge case (let's not go into the exec-injection problems of <> itself…); I'm glad Perl 6 is more like Python in this example - sane boolification and built-in line iterators that handle EOF/"" properly.

The PEP 572 endgame

Posted Jul 24, 2018 15:09 UTC (Tue) by spaetz (guest, #32870) [Link]

while ((c = getchar()) != EOF) lines += c == '\n';
doesn't have a direct equivalent in Python.

Pfewww, and that makes me very happy. Seriously, how many subtle ways to shoot yourself in the foot does the above line provide?

The PEP 572 endgame

Posted Jul 25, 2018 9:49 UTC (Wed) by jwilk (subscriber, #63328) [Link] (2 responses)

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

Posted Jul 25, 2018 12:55 UTC (Wed) by rweikusat2 (subscriber, #117920) [Link] (1 responses)

I think everybody got it now.

It's still irrelevant.

The PEP 572 endgame

Posted Jul 25, 2018 13:14 UTC (Wed) by rahulsundaram (subscriber, #21946) [Link]

> It's still irrelevant.

Why is it irrelevant?

The PEP 572 endgame

Posted Jul 21, 2018 20:44 UTC (Sat) by k8to (guest, #15413) [Link]

I'm entirely with you.

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.

The PEP 572 endgame

Posted Jul 20, 2019 17:21 UTC (Sat) by lkcl (guest, #60496) [Link] (1 responses)

> I find I have to read lines with these expressions both forwards
> and then backwards to understand what is going on.

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.

The PEP 572 endgame

Posted Jul 23, 2019 15:07 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

I remember that for Python3, Guido wanted to remove map, filter, and reduce because he didn't think they were very Pythonic (in favor of just using list comprehensions). Personally, I don't find Python's list comprehensions to be that crazy in practice. One issue I do have is the ambiguity of where an `if` fragment attaches within the loop, but I think the Python answer there is "decompose your code more" to make it simpler in the first place. Especially now that they form can generators rather than structures directly.

The PEP 572 endgame

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.

The PEP 572 endgame

Posted Jul 18, 2018 19:44 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

How about a Battle Royale to death? Seems appropriate given the length of these threads.

The PEP 572 endgame

Posted Jul 18, 2018 19:50 UTC (Wed) by dunlapg (guest, #57764) [Link]

At least that would make sure people aren't bike-shedding!

The PEP 572 endgame

Posted Jul 20, 2018 0:05 UTC (Fri) by gerdesj (subscriber, #5446) [Link]

Thought experiment: Imagine a PEP in an alternative world that suggests eschewing the existing angle bracket (< >) based syntax in favour of simply using whitespace:

a <
... stuff ...
>
b <
... more stuff ...
>

becomes:

a
... stuff ...
b
... more stuff ...

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.)

The PEP 572 endgame

Posted Jul 20, 2018 1:41 UTC (Fri) by OrbatuThyanD (guest, #114326) [Link] (1 responses)

getting a pep accepted turned me off of python. the python-dev@ community is toxic.

The PEP 572 endgame

Posted Jul 23, 2018 22:39 UTC (Mon) by vstinner (subscriber, #42675) [Link]

Would you mind to elaborate? What was the PEP? I am worried that you call the list toxic. What can we do to make it less toxic?

The PEP 572 endgame

Posted Jul 24, 2018 20:47 UTC (Tue) by acarno (subscriber, #123476) [Link]

I wonder how a forum format -- with ycombinator-style points (hidden from users) and reddit-style sorting (see [1]) -- would work for PEP discussions. In theory it would push bike shedders down and promote useful technical arguments.

[1] https://redditblog.com/2009/10/15/reddits-new-comment-sor...


Copyright © 2018, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds