
How PEP-572 would change the standard library - rbanffy
https://github.com/python/cpython/pull/8122/files
======
cnorthwood
It's going to look quite alien at first, but this is a great demonstration of
how this will actually help the language. The regexp examples are a common one
always felt overly verbose when I do that. The risk is that it makes it very
easy to write overly terse code, I don't think I'm the only dev who's ever
written a "clever" list comprehension only to come back to it later and
struggle to get my head round it.

~~~
nerdponx
Seeing this in action, I think I kind of hate it. One of the basic aspects of
idiomatic Python that I like the most is the fact that doing multiple things
at once is discouraged. Multiple assignment (x,y = a,b) is possible but not
typically done except where it conveys special intent.

This seems to be replacing

    
    
        foo = check_foo()
        if foo:
            do_foo()
    

with

    
    
        if (foo := check_foo()):
            do_foo()
    

which goes against that idea.

~~~
misnome
This _can_ replace that, sure, but I think it's more meant to be replacing
things like:

    
    
        foo = check_foo()
        while foo:
            do_foo()
            foo = check_foo()
    

With

    
    
        while foo := check_foo():
            do_foo()
    

which is a case that hasn't had an elegant solution in the past. Another, if
example, where you want to call a function based on the result of a check, but
don't want to make that check more than once:

    
    
        foo = check_foo()
        if foo:
            do_foo(foo)
        else:
            other_foo = check_other_foo()
            if other_foo:
                do_other_foo(other_foo)
            else:
                # Continue
    

with

    
    
        if foo := check_foo():
            do_foo(foo)
        elif other_foo := check_other_foo()
            do_other_foo(other_foo)
        elif .....:
    

Yes, all of this has been possible and there are other ways to do it, but this
expresses the problem very concisely and has less nesting at the cost of a
slightly more complicated syntax. I find these rewritten examples easier to
parse than the originals.

Pulling things out of comprehensions is pretty neat too.

Other than a few cases (e.g. these) I don't see this suited or intended for
extremely wide usage, but no worse than any other complex idiom that people
can overuse.

~~~
mehrdadn
I agree the check_foo() duplication was bad, but was this workaround really so
bad?

    
    
        while 1:
            foo = check_foo()
            if not foo: break
            do_foo(foo)
    

Its only downside was taking 2 extra lines, not actual understandability.

~~~
int_19h
It makes the intent of the loop less clear, since now the condition is tucked
away somewhere in the middle of the body, even though it's really a
precondition.

~~~
mehrdadn
I'm torn on this, because although it's easy to say that for a toy example
like this, when these "preconditions" evolve over time, you get things like:

    
    
        while 1:
            now = time()
            if now > prev_time + UPDATE_TIME: report_progress()
            foo = check_foo()
            if not foo: break
            if foo < 0: print_error(); break
            do_foo(foo)
            prev_time = now
    

Now, was it really dictating a precondition? Or was it actually dictating a
postcondition? Or both or neither? Would you really want these responses to
dictate the locations of the condition in the _syntax_? Because it seems to me
that forcing yourself to put these conditions in the loop condition just makes
the code extremely ugly and difficult to evolve (requiring you to duplicate
code or have very convoluted expressions with assignments).

------
mehrdadn
This looks awful. It was an _upside_ of Python that it had simple syntax, so
that it had less cognitive overload. Without at least adding proper scoping
and variable declarations to at least add functionality, having two assignment
syntaxes seems worse than not having it at all.

------
akvadrako
This is so ugly, especially the C-like parens. I wish they had gone with the
pythonic _as_ syntax:

    
    
       if rawdata.count("\n", i, j) as nlines:
          self.lineno = self.lineno + nlines
          ...

~~~
raverbashing
Using 'as' is prettier, but a pain to read, as your eyes have to go all the
way to the end of the line

You can still use the old way if you think this new one is ugly, but to be
honest I think it's going to be an improvement.

~~~
wodenokoto
I don't understan what you mean that you have to "go all the way to the end".

with

    
    
        if my_data := my_function():
            do_stuff(my_data)
    

the left-hand side of the assignment is completely irrelevant to the if
statement. Here you have to go "all the way to the end" just to see what that
clause of the if-statement is.

With the _as_ -syntax, the if-statement and the clause are placed together.

However, I do find the if-as sentence kinda weird, when trying to understand
it using normal english.

~~~
mehrdadn
> the left-hand side of the assignment is completely irrelevant to the if
> statement

I guess is subjective? To me, that's the first thing I want to know about an
assignment -- what is being assigned to? Especially if you try to keep your
programs reasonably functional, then it's in fact _the_ thing that matters,
since the right-hand side would be side-effect-free.

Another way I see it is that an assignment expression is like solving a
problem: first you'd ask "what did they solve for?", then you ask "how did
they solve for it?" \-- not the other way around.

------
hchasestevens
Note that this doesn't actually encompass all the possible usages of the
PEP-572 operator in the standard library, of which there appear to be 312 more
candidates (and a further 100 in the rest of CPython):

    
    
      root@8d88e1e678b0:/cpython# astpath -A 1 "//Assign[(targets/Name/@id = following-sibling::*[1][name(.) = 'If']/test/Name/@id)]" > pep-572.txt
      root@8d88e1e678b0:/cpython# grep "Lib" pep-572.txt | head -20
      ./Lib/argparse.py:284   >        help = self._root_section.format_help()
      ./Lib/argparse.py:285            if help:
      ./Lib/argparse.py:2275  >        a = [action for action in positionals
      ./Lib/argparse.py:2276                if action.nargs in [PARSER, REMAINDER]]
      ./Lib/cmd.py:300        >                    doc=getattr(self, 'do_' + arg).__doc__
      ./Lib/cmd.py:301                             if doc:
      ./Lib/cmd.py:356        >        nonstrings = [i for i in range(len(list))
      ./Lib/cmd.py:357                                 if not isinstance(list[i], str)]
      ./Lib/compileall.py:123 >        mo = rx.search(fullname)
      ./Lib/compileall.py:124          if mo:
      ./Lib/dis.py:386        >    show_lineno = linestarts is not None
      ./Lib/dis.py:387             if show_lineno:
      ./Lib/dis.py:403        >        new_source_line = (show_lineno and
      ./Lib/dis.py:404                                    instr.starts_line is not None and
      ./Lib/enum.py:86        >                already = set(value) & set(self._member_names)
      ./Lib/enum.py:87                         if already:
      ./Lib/enum.py:153       >        invalid_names = set(enum_members) & {'mro', }
      ./Lib/enum.py:154                if invalid_names:
      ./Lib/enum.py:848       >    negative = value < 0
      ./Lib/enum.py:849            # issue29167: wrap accesses to _value2member_map_ in a list to avoid race
      root@8d88e1e678b0:/cpython# grep -E "\.py\:[^w]+>" pep-572.txt | wc -l
      412
      root@8d88e1e678b0:/cpython# grep -E "/Lib/.+\.py\:[^w]+>" pep-572.txt | wc -l
      312

------
tomtimtall
For some reason the opposition on this matter is really vocal. I’ll make the
controversial but extremely low risk prediction that this feature won’t be the
death of Python and that most will come to find it relatively harmless like
fstrings once the pundits are done milking it.

~~~
zimablue
I think a large part of it is that if you've read thousands of lines of python
code and have all the syntax in your head, it's painful and annoying to
suddenly be reading python code that is and looks different. I found this
commit really grating to read somehow but I reckon in 3 months it'll be
natural.

------
unwind
Very nice, although I would hate to be the one to review that patch. A large
number of small "fiddly" changes, it's easy to be afraid that copy/paste
occurred and that some error snuck in.

~~~
aroberge
That patch is not meant to be applied. It has been created to demonstrate the
difference that the new syntax would make on code actually used in production.

------
wodenokoto
As someone who is not very good with grammars of programming languages, what
is wrong with having

    
    
        if a = b():
           foo(a)
    

I know it doesn't work, but why couldn't it?

~~~
misnome
This is addressed in the PEP: [https://www.python.org/dev/peps/pep-0572/#why-
not-just-turn-...](https://www.python.org/dev/peps/pep-0572/#why-not-just-
turn-existing-assignment-into-an-expression). Their argument seems to be that
'if a = b()' is too similar to 'if a == b():' and thus doing that would cause
more mistakes, whereas at the moment (?) they are caught by the parser.

~~~
AlexandrB
Yes. This mistake is very common in C. So much so that it's considered good
practice to put the constant first when doing equality comparisons:

    
    
        if (10 == x)
    

This way accidentally missing a "=" triggers a compiler error instead of
quietly doing the wrong thing.

~~~
sorenjan
Called Yoda conditions.

[https://en.wikipedia.org/wiki/Yoda_conditions](https://en.wikipedia.org/wiki/Yoda_conditions)

------
crististm
Am I wrong to assume there was nothing wrong with the previous code and these
changes are gratuitous (e.g. just because we can)?

~~~
typon
Decorators, context managers, list comprehensions, multiple assignments,
keyword arguments are all gratuitous. Should we remove them?

~~~
crististm
Which ones are you referring to?

~~~
typon
These are all merely syntactic sugar that replicate functionality available in
Python that would otherwise be done in a more awkward way. This PEP is doing
the same thing.

~~~
crististm
My question is about the value of this particular patch-set not about
usefulness of the PEP.

"The point of this PR is just to open discuss on coding style: discuss when
assignment expressions are appropriate or not."

There is no new code and the author's intention seems to be more than a "let's
see how it would look like" patch. (Discussing where to use new syntax on
existing code is a waste of time unless the intent is to apply the changes)

------
mrastro
`do while` loops are (effectively) coming to Python. Yay!

~~~
2T1Qka0rEiPr
I was going to say - its application in this while loop is really nice I
think:

[https://github.com/python/cpython/pull/8122/files#diff-0ad86...](https://github.com/python/cpython/pull/8122/files#diff-0ad86c44e7866421ecaa5ad2c0edb0e2L946)

~~~
mehrdadn
Unfortunately it's also directly at odds with Python's own EAFP [1]
philosophy, which is to raise exceptions rather than return special values
(StopIteration etc.). It's no longer clear to me which one the proper Python
coding style is. Are we supposed to switch to returning special values instead
of raising exceptions now?

[1] [https://docs.quantifiedcode.com/python-anti-
patterns/readabi...](https://docs.quantifiedcode.com/python-anti-
patterns/readability/asking_for_permission_instead_of_forgiveness_when_working_with_files.html)

~~~
StavrosK
Where do you see it returning special values? While loops have always stopped
on a falsy value.

~~~
mehrdadn
The issue is the readBlah() methods. What should they do when they can't read
anything? They can return either 0 or an empty result (non-special falsy
value), return -1 (special truthy value), return None (special falsy value),
or raise an exception. In this case it seems they chose the first option, but
this is at odds with something like next(iterator), which similar but raises
an exception. It's not clear to me which is the proper pattern anymore, now
that they're encouraging assignment.

------
rurban
Awful syntax. If then =?, not =: which already is the pascal assignment and
perl binding operator.

~~~
Annatar
Welcome to Python, where proponents will proudly claim that the language is
easy to read and intuitive.

I was reading the discussion above, where they argue whether a statement like

    
    
      if (foo := check_foo()):
            do_foo()
    

is readable. The entire time I’m thinking Willis. Tango. Foxtrot! This is the
kind of stuff us C / AWK programmers do all of the time precisely because it’s
concise, elegant and intuitive (not to mention well understood), and these
guys are struggling since it turned into a huge discussion. Made me wonder if
we’re in a programming kindergarten here or what?

~~~
int_19h
This is also the kind of stuff that you C programmers write bugs with all the
time, by mistaking = with ==; to the point where most C compilers these days
would just flag something like if(a=1) as a warning with default settings.

Really, all this mess is the legacy of poor choice of = and == as operators
back in C (or rather, B). Pascal really had it right with the assignment being
very distinct from comparison, such that it's pretty much impossible to
confuse the two. The sole reason for that design decision in C, so far as I
know, was that assignments occur slightly more often than comparisons, and so
they wanted it to be a single character. But I seriously doubt that it was
worth all the bugs.

~~~
Annatar
“This is also the kind of stuff that you C programmers write bugs with all the
time, by mistaking = with ==; to the point where most C compilers these days
would just flag something like if(a=1) as a warning with default settings.”

No it isn’t; we’re not beginners.

I learned Pascal before I learned C. Hated the retarded := as superflous.

~~~
pseudalopex
If that were true, exist Yoda conditions would not.

------
dig1
Didn't read this pep, but have they considered C-like assignment, like:

    
    
        if (foo = func()) is not None:
            ...
    

I find this more readable and uniform than using := construct.

~~~
duckerude
Because it's easy to accidentally type foo = func() when you meant foo ==
func(). That mistake happens so often in C that modern compilers warn you
unless you put an extra set of parentheses around it.

It would also cause ambiguity with keyword arguments. You'd have to write
foo((x = 3)) because foo(x = 3) already has a meaning. I think foo(x := 3) is
clearer.

Maybe it would have been best to use := for all assignment. But it's too late
for that.

