[Python-Dev] (name := expression) doesn't fit the narrative of PEP 20

1,223 views
Skip to first unread message

Łukasz Langa

unread,
Apr 25, 2018, 4:23:05 PM4/25/18
to Python-Dev@Python. Org
PEP 572 caused a strong emotional reaction in me. I wanted to first understand
my intuitive objection to the idea before posting anything.

I feel that (name := expression) doesn't fit the narrative of PEP 20. It
doesn't remove complexity, it only moves it. What was its own assignment before
now is part of the logic test. This saves on vertical whitespace but makes
parsing and understanding logic tests harder. This is a bad bargain: logic
tests already contain a lot of complexity that human readers have to cope with.

Proponents of := argue it makes several patterns flatter (= better than nested)
to express. Serial regular expression matching is a popular example. However,
(name := expression) itself is making logic tests more nested, not flatter. It
makes information in the logic test denser (= worse than sparse). Since it also
requires an additional pair of parentheses, it forces the reader to decompose
the expression in their head.

:= also goes against having one obvious way to do it. Since it's an expression,
it can also be placed on its own line or in otherwise weird places like
function call arguments. I anticipate PEP 8 would have to be extended to
explicitly discourage such abuse. Linters would grow rules against it. This is
noise.

I'm -1 on PEP 572, I think it's very similar in spirit to the rejected PEP 463.

-- Ł

signature.asc

Chris Angelico

unread,
Apr 25, 2018, 4:26:47 PM4/25/18
to Python-Dev@Python. Org
On Thu, Apr 26, 2018 at 6:21 AM, Łukasz Langa <luk...@langa.pl> wrote:
> := also goes against having one obvious way to do it. Since it's an expression,
> it can also be placed on its own line or in otherwise weird places like
> function call arguments. I anticipate PEP 8 would have to be extended to
> explicitly discourage such abuse. Linters would grow rules against it. This is
> noise.

Does this argument also apply to the if/else expression? Do linters
need rules to advise against people writing code like:

print(x) if x is None else print(y)

? It's perfectly legal to write code like this. But I don't see people
abusing this sort of thing.

ChrisA
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Guido van Rossum

unread,
Apr 25, 2018, 4:30:52 PM4/25/18
to Łukasz Langa, Python-Dev@Python. Org
A very emotional appeal, you don't seem to grasp the usability improvements this will give. I hear you but at this point appeals to Python's "Zen" don't help you.

_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev

Łukasz Langa

unread,
Apr 25, 2018, 4:57:24 PM4/25/18
to Guido van Rossum, Python-Dev@Python. Org

> On 25 Apr, 2018, at 1:28 PM, Guido van Rossum <gu...@python.org> wrote:
>
> You don't seem to grasp the usability improvements this will give. I hear you but at this point appeals to Python's "Zen" don't help you.

This reads dismissive to me. I did read the PEP and followed the discussion on
python-dev. I referred to PEP 20 because it distills what's unique about the
value proposition of Python. It's our shared vocabulary.

Can you address the specific criticism I had? To paraphrase it without PEP 20
jargon:

> (name := expression) makes code less uniform. It inserts more information
> into a place that is already heavily packed with information (logic tests).

-- Ł

signature.asc

Łukasz Langa

unread,
Apr 25, 2018, 5:08:33 PM4/25/18
to Chris Angelico, Python-Dev@Python. Org

> On 25 Apr, 2018, at 1:24 PM, Chris Angelico <ros...@gmail.com> wrote:
>
> On Thu, Apr 26, 2018 at 6:21 AM, Łukasz Langa <luk...@langa.pl> wrote:
>> := also goes against having one obvious way to do it. Since it's an expression,
>> it can also be placed on its own line or in otherwise weird places like
>> function call arguments. I anticipate PEP 8 would have to be extended to
>> explicitly discourage such abuse. Linters would grow rules against it. This is
>> noise.
>
> Does this argument also apply to the if/else expression? Do linters
> need rules to advise against people writing code like:
>
> print(x) if x is None else print(y)
>
> ? It's perfectly legal to write code like this. But I don't see people
> abusing this sort of thing.

Ternary expressions are different because their flow is deliberately different
from a regular if statement. It's also different from the C equivalent.
`:=` on the other hand is deciptively similar to `=`.

But yeah, I think worrying about abuse of the feature is a red herring. The
gist of my criticism of your PEP is about the decreased balance in information
density.

-- Ł
signature.asc

Tim Peters

unread,
Apr 25, 2018, 5:57:47 PM4/25/18
to Łukasz Langa, Python-Dev@Python. Org
[Guido]

>> You don't seem to grasp the usability improvements this will give.
>> I hear you but at this point appeals to Python's "Zen" don't help you.

[Łukasz Langa <luk...@langa.pl>]


> This reads dismissive to me. I did read the PEP and followed the discussion on
> python-dev. I referred to PEP 20 because it distills what's unique about the
> value proposition of Python. It's our shared vocabulary.
>
> Can you address the specific criticism I had? To paraphrase it without PEP 20
> jargon:

> (name := expression) makes code less uniform. It inserts more information
> into a place that is already heavily packed with information (logic tests).

I'll take a crack at that. It's not about "head arguments" at all. I
sat out the first hundred messages about this on python-ideas, and
looked at code instead. What I found had little to do with any of the
head (abstract) arguments passionately debated for the duration ;-)

In real life, I found a great many conditional tests that not only
weren't "heavily packed" with information, they were simply of the
form:

NAME = expression
if NAME:
... use NAME ...

That looks more like assembly language than Python ;-) I saw no harm
at all, and a little gain, in

if NAME := expression:
... use NAME ...

instead. But even a little gain adds up when it happens so often.

Of course there have been better examples given of bigger gains. But
in no case have the tests in those examples been "heavily packed with
information". If they had been, I would have suggested instead
breaking the test clauses _out_ of the conditional statements, and
giving them names each on their own dedicated lines, with comments
explaining what the heck the _intents_ are, even at the cost of adding
an indentation level or two. Sometimes conditionals are _already_ "too
dense". But more often they're very sparse.

This becomes a question of seasoned judgment. For example, here's a
real loop summing a series expansion, until the new terms become so
small they make no difference to the running total (a common enough
pattern in code slinging floats or decimals):

while True:
old = total
total += term
if old == total:
return total
term *= mx2 / (i*(i+1))
i += 2

To my eyes, this is genuinely harder to follow, despite its relative brevity:

while total != (total := total + term):
term *= mx2 / (i*(i+1))
i += 2
return total

So I wouldn't use binding expressions in that case. I don't have a
compelling head argument for _why_ I find the latter spelling harder
to follow, but I don't need a theory to know that I in fact do.

But neither do I need a compelling head argument for "why" to know
that in many other cases I find that the use of binding expressions
improves the code. You shouldn't believe me even if I pretended to
have one and passionately argued for it. But, by the same token, I'm
spectacularly unmoved by other peoples' head arguments.

For that reason, the messages that sway me are those showing real
code, or at least plausibly realistic code. In the majority of those
so far, binding expressions would be a small-to-major win.


_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev

Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Guido van Rossum

unread,
Apr 25, 2018, 5:58:47 PM4/25/18
to Łukasz Langa, Python-Dev@Python. Org
On Wed, Apr 25, 2018 at 1:55 PM, Łukasz Langa <luk...@langa.pl> wrote:

> On 25 Apr, 2018, at 1:28 PM, Guido van Rossum <gu...@python.org> wrote:
>
> You don't seem to grasp the usability improvements this will give. I hear you but at this point appeals to Python's "Zen" don't help you.

This reads dismissive to me. I did read the PEP and followed the discussion on
python-dev.

It was meant dismissive. With Chris, I am tired of every core dev starting their own thread about how PEP 572 threatens readability or doesn't reach the bar for new syntax (etc.). These arguments are entirely emotional and subjective.

And that's how big decisions get made. Nobody can predict the outcome with sufficient accuracy. It's like buying a new car or house. In the end you decide with your gut.
 
I referred to PEP 20 because it distills what's unique about the
value proposition of Python. It's our shared vocabulary.

It's poetry, not a set of axioms. You can't *prove* anything with an appeal to PEP 20. You can appeal to it, for sure, but such an appeal *by definition* is subjective and emotional. (There's Only One Way To Do It? Give me a break. :-)
 
Can you address the specific criticism I had? To paraphrase it without PEP 20
jargon:

> (name := expression) makes code less uniform.  It inserts more information
> into a place that is already heavily packed with information (logic tests).

Most Python features make code less uniform in order to make it less repetitive. (Who needs classes? :-)

Antoine Pitrou

unread,
Apr 25, 2018, 6:10:32 PM4/25/18
to pytho...@python.org
On Wed, 25 Apr 2018 16:55:43 -0500
Tim Peters <tim.p...@gmail.com> wrote:
>
> To my eyes, this is genuinely harder to follow, despite its relative brevity:
>
> while total != (total := total + term):

Does it even work? Perhaps if the goal is to stop when total is NaN,
but otherwise?

> For that reason, the messages that sway me are those showing real
> code, or at least plausibly realistic code. In the majority of those
> so far, binding expressions would be a small-to-major win.

I'm sure it's possible to find thousands of line of code where binding
expressions wouldn't be a win, but I'm not sure that would be a
constructive use of mailing-list bandwidth.

Regards

Antoine.

Ethan Furman

unread,
Apr 25, 2018, 6:14:47 PM4/25/18
to pytho...@python.org
On 04/25/2018 02:55 PM, Tim Peters wrote:

> This becomes a question of seasoned judgment. For example, here's a
> real loop summing a series expansion, until the new terms become so
> small they make no difference to the running total (a common enough
> pattern in code slinging floats or decimals):
>
> while True:
> old = total
> total += term
> if old == total:
> return total
> term *= mx2 / (i*(i+1))
> i += 2
>
> To my eyes, this is genuinely harder to follow, despite its relative brevity:
>
> while total != (total := total + term):
> term *= mx2 / (i*(i+1))
> i += 2
> return total
>
> So I wouldn't use binding expressions in that case. I don't have a
> compelling head argument for _why_ I find the latter spelling harder
> to follow, but I don't need a theory to know that I in fact do.

I know why I do: I see "while total != total" and my gears start stripping. On the other hand,

while total != (total + term as total):
...

I find still intelligible. (Yes, I know "as" is dead, just wanted to throw that out there.)

--
~Ethan~

Steven D'Aprano

unread,
Apr 25, 2018, 6:20:24 PM4/25/18
to pytho...@python.org
On Wed, Apr 25, 2018 at 01:55:37PM -0700, Łukasz Langa wrote:
>
> > On 25 Apr, 2018, at 1:28 PM, Guido van Rossum <gu...@python.org> wrote:
> >
> > You don't seem to grasp the usability improvements this will give. I hear you but at this point appeals to Python's "Zen" don't help you.
>
> This reads dismissive to me. I did read the PEP and followed the discussion on
> python-dev. I referred to PEP 20 because it distills what's unique about the
> value proposition of Python. It's our shared vocabulary.

Every programming language has a shared vocabulary. That's hardly unique
to Python.


> Can you address the specific criticism I had? To paraphrase it without PEP 20
> jargon:
>
> > (name := expression) makes code less uniform. It inserts more information
> > into a place that is already heavily packed with information (logic tests).

I'm not Guido, but I'll make an attempt.

I think the comment about "less uniform" isn't meaningful. Uniform in
what way?

I don't even know how to interpret the uniform comment here, unless you
mean to imply that every statement and expression in Python currently
has the same information density, and binding-expressions will violate
that. That's clearly not the case, so I'm left puzzled by what you mean.

As for your observation that binding-expressions don't reduce
complexity, they merely move it, I think you may be right. But
then it is a truism that complexity is never reduced, only moved, so
that's likely to be true for any feature (including existing ones).
Should we move back to assembly language programming because Python
hasn't reduced complexity, only moved it? I don't think so.

Clearly binding-expressions do add a little more complexity to the
language, and they do move code from vertically separated statements to
horizontally laid-out expressions.

But why is this necessarily a bad thing? Exactly the same complaint can
be made about comprehensions, and look at how wildly successful they
have been.

Offset against the increase in horizontal complexity is a corresponding
decrease in vertical complexity, and that's beneficial.

Whatever cost they have has to be offset against the benefits, and I
think the feature will come ahead on the plus side overall. Of course,
like any syntactic feature, it may be abused by those who (by accident
or design) write obfuscated or excessively complex code. We shouldn't
ignore that risk, but nor should we use that as an excuse to dismiss the
feature's benefits.


--
Steve


_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev

Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Tim Peters

unread,
Apr 25, 2018, 6:24:29 PM4/25/18
to Antoine Pitrou, Python Dev
[Tim]
>> To my eyes, this is genuinely harder to follow, despite its relative brevity:
>>
>> while total != (total := total + term):

[Antoine]
> Does it even work? Perhaps if the goal is to stop when total is NaN,
> but otherwise?

I don't follow you. You snipped all the text explaining why it would
work, so trying reading that again? When, e.g., `total` reaches 1.0
and `term` reaches 1e-30, this becomes:

while 1.0 != (total := 1.0 + 1-e30):

which leaves `total` unchanged (1.0 + 1e-30 == 1.0) and then

while 1.0 != 1.0:

causes the loop to exit (`while False:`).


>> For that reason, the messages that sway me are those showing real
>> code, or at least plausibly realistic code. In the majority of those
>> so far, binding expressions would be a small-to-major win.

> I'm sure it's possible to find thousands of line of code where binding
> expressions wouldn't be a win, but I'm not sure that would be a
> constructive use of mailing-list bandwidth.

And that "argument" is? ;-)

Note that I managed to move the PEP _away_ from general "assignment
expressions" to the much simpler "binding expressions" precisely _by_
illustrating, via real code, why the generality of the former wasn't
actually useful in any case I looked at. If something is always - or
almost always - useless, that can be shown via considering realistic
code. That was far more productive than endless abstract debates.

Ethan Furman

unread,
Apr 25, 2018, 6:34:55 PM4/25/18
to pytho...@python.org
Having said that, since whomever mentioned reading ":=" as "which is", I'm good with ":=".

Chris Angelico

unread,
Apr 25, 2018, 6:40:37 PM4/25/18
to python-dev
On Thu, Apr 26, 2018 at 8:08 AM, Antoine Pitrou <soli...@pitrou.net> wrote:
> On Wed, 25 Apr 2018 16:55:43 -0500
> Tim Peters <tim.p...@gmail.com> wrote:
>>
>> To my eyes, this is genuinely harder to follow, despite its relative brevity:
>>
>> while total != (total := total + term):
>
> Does it even work? Perhaps if the goal is to stop when total is NaN,
> but otherwise?

Yes, it does, because the first "total" is looked up before the
rebinding happens. It's 100% unambiguous to the compiler... but still
pretty unclear to a human. And I think the multiple use of 'total' is
to blame for that. So I agree with Tim that this particular example is
better in longhand.

ChrisA

Guido van Rossum

unread,
Apr 25, 2018, 6:41:23 PM4/25/18
to Ethan Furman, Python-Dev
On Wed, Apr 25, 2018 at 3:15 PM, Ethan Furman <et...@stoneleaf.us> wrote:
On 04/25/2018 02:55 PM, Tim Peters wrote:
To my eyes, this is genuinely harder to follow, despite its relative brevity:

         while total != (total := total + term):
             term *= mx2 / (i*(i+1))
             i += 2
         return total

So I wouldn't use binding expressions in that case.  I don't have a
compelling head argument for _why_ I find the latter spelling harder
to follow, but I don't need a theory to know that I in fact do.

I know why I do:  I see "while total != total" and my gears start stripping.  On the other hand,

  while total != (total + term as total):
     ...

I find still intelligible.  (Yes, I know "as" is dead, just wanted to throw that out there.)

The problem with either variant is that they hinge on subtle left-to-right evaluation rules. Python tries to promise left-to-right evaluation "except when it doesn't apply", e.g. in assignments the RHS is typically evaluated before subexpressions in the LHS: a[f()] = g() calls g() before f().

The example is supposed to load the left operand to != on the stack before loading the right operand, but the rule that says the left operand is evaluated before the right operand is much weaker than other evaluation order rules (like the rule stating that the arguments are evaluated before the function is called -- and before you laugh, in Algol-60 that wasn't always the case).

This argument applies regardless of which syntactic form you use, and no matter what we choose, the PEP will have to clarify evaluation order in more cases than the current reference manual. (IIRC Nathaniel brought this up.)
 

Antoine Pitrou

unread,
Apr 25, 2018, 6:46:36 PM4/25/18
to pytho...@python.org
On Thu, 26 Apr 2018 08:38:51 +1000
Chris Angelico <ros...@gmail.com> wrote:

> On Thu, Apr 26, 2018 at 8:08 AM, Antoine Pitrou <soli...@pitrou.net> wrote:
> > On Wed, 25 Apr 2018 16:55:43 -0500
> > Tim Peters <tim.p...@gmail.com> wrote:
> >>
> >> To my eyes, this is genuinely harder to follow, despite its relative brevity:
> >>
> >> while total != (total := total + term):
> >
> > Does it even work? Perhaps if the goal is to stop when total is NaN,
> > but otherwise?
>
> Yes, it does, because the first "total" is looked up before the
> rebinding happens. It's 100% unambiguous to the compiler... but still
> pretty unclear to a human. And I think the multiple use of 'total' is
> to blame for that. So I agree with Tim that this particular example is
> better in longhand.

"Better" is an understatement :-( Now that I understood it (thanks
for the explanation), the shorthand version appears completely bonkers.

Regards

Antoine.

Tim Peters

unread,
Apr 25, 2018, 7:58:02 PM4/25/18
to Antoine Pitrou, Python Dev
[Tim]
>>>> To my eyes, this is genuinely harder to follow, despite its relative brevity:
>>>>
>>>> while total != (total := total + term):

[Antoine]
>>> Does it even work? Perhaps if the goal is to stop when total is NaN,
>>> but otherwise?

[Chris]
>> Yes, it does, because the first "total" is looked up before the
>> rebinding happens. It's 100% unambiguous to the compiler... but still
>> pretty unclear to a human. And I think the multiple use of 'total' is
>> to blame for that. So I agree with Tim that this particular example is
>> better in longhand.

[Antoine]
> "Better" is an understatement :-( Now that I understood it (thanks
> for the explanation),

Ah, sorry - I had no idea it was the "left to right evaluation" part
you weren't seeing. Next time explain why you think something is
broken?


> the shorthand version appears completely bonkers.

I wouldn't go that far, but I already said I wouldn't write it that way.

However, without looking at real code, people are just flat-out
guessing about how bad - or good - things _can_ get, no matter how
confident they sound.

So at least give me credit for presenting the _worst_ brief
binding-expression example you've seen too ;-)

Yury Selivanov

unread,
Apr 25, 2018, 8:13:02 PM4/25/18
to Guido van Rossum, Python-Dev
On Wed, Apr 25, 2018 at 5:58 PM Guido van Rossum <gu...@python.org> wrote:
[..]
> It was meant dismissive. With Chris, I am tired of every core dev
starting their own thread about how PEP 572 threatens readability or
doesn't reach the bar for new syntax (etc.). These arguments are entirely
emotional and subjective.

FWIW I started my thread for allowing '=' in expressions to make sure that
we fully explore that path. I don't like ':=' and I thought that using '='
can make the idea more appealing to myself and others. It didn't, sorry if
it caused any distraction. Although adding a new ':=' operator isn't my main
concern.

I think it's a fact that PEP 572 makes Python more complex.
Teaching/learning Python will inevitably become harder, simply because
there's one more concept to learn.

Just yesterday this snippet was used on python-dev to show how great the
new syntax is:

my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))

To my eye this is an anti-pattern. One line of code was saved, but the
other line becomes less readable. The fact that 'buf' can be used after
that line means that it will be harder for a reader to trace the origin of
the variable, as a top-level "buf = " statement would be more visible.

The PEP lists this example as an improvement:

[(x, y, x/y) for x in input_data if (y := f(x)) > 0]

I'm an experienced Python developer and I can't read/understand this
expression after one read. I have to read it 2-3 times before I trace where
'y' is set and how it's used. Yes, an expanded form would be ~4 lines
long, but it would be simple to read and therefore review, maintain, and
update.

Assignment expressions seem to optimize the *writing code* part, while
making *reading* part of the job harder for some of us. I write a lot of
Python, but I read more code than I write. If the PEP gets accepted I'll
use
the new syntax sparingly, sure. My main concern, though, is that this PEP
will likely make my job as a code maintainer harder in the end, not easier.

I hope I explained my -1 on the PEP without sounding emotional.

Thank you,
Yury




Yury
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Antoine Pitrou

unread,
Apr 25, 2018, 8:16:07 PM4/25/18
to pytho...@python.org
On Wed, 25 Apr 2018 18:55:56 -0500
Tim Peters <tim.p...@gmail.com> wrote:
>
> > the shorthand version appears completely bonkers.
>
> I wouldn't go that far, but I already said I wouldn't write it that way.
>
> However, without looking at real code, people are just flat-out
> guessing about how bad - or good - things _can_ get, no matter how
> confident they sound.
>
> So at least give me credit for presenting the _worst_ brief
> binding-expression example you've seen too ;-)

I had no idea you were a bit short on them, so I'll gladly give you
credits for it :-) But I hope you'll use them responsibly!

Regards

Antoine.

Chris Angelico

unread,
Apr 25, 2018, 8:22:27 PM4/25/18
to Python-Dev
On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
<yseliv...@gmail.com> wrote:
> Just yesterday this snippet was used on python-dev to show how great the
> new syntax is:
>
> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
>
> To my eye this is an anti-pattern. One line of code was saved, but the
> other line becomes less readable. The fact that 'buf' can be used after
> that line means that it will be harder for a reader to trace the origin of
> the variable, as a top-level "buf = " statement would be more visible.

Making 'buf' more visible is ONLY a virtue if it's going to be used
elsewhere. Otherwise, the name 'buf' is an implementation detail of
the fact that this function wants both a buffer and a size. Should you
want to expand this out over more lines, you could do this:

template = [None]
buf = template*get_size()
length = len(buf)
my_func(arg, buffer=buf, size=length)

What are the names 'template' and 'length' achieving? Why should they
claim your attention? They are useless relics of a done-and-dusted
calculation, being retained for no reason. They do not deserve
top-level placement.

The form as given above is starting to get a bit noisy, but I strongly
disagree that 'buf' deserves to be a stand-alone name. It is as
valueless as 'template' is.

ChrisA

Antoine Pitrou

unread,
Apr 25, 2018, 8:43:41 PM4/25/18
to pytho...@python.org
On Thu, 26 Apr 2018 10:20:40 +1000
Chris Angelico <ros...@gmail.com> wrote:
> On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
> <yseliv...@gmail.com> wrote:
> > Just yesterday this snippet was used on python-dev to show how great the
> > new syntax is:
> >
> > my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
> >
> > To my eye this is an anti-pattern. One line of code was saved, but the
> > other line becomes less readable. The fact that 'buf' can be used after
> > that line means that it will be harder for a reader to trace the origin of
> > the variable, as a top-level "buf = " statement would be more visible.
>
> Making 'buf' more visible is ONLY a virtue if it's going to be used
> elsewhere. Otherwise, the name 'buf' is an implementation detail of
> the fact that this function wants both a buffer and a size. Should you
> want to expand this out over more lines, you could do this:
>
> template = [None]
> buf = template*get_size()
> length = len(buf)
> my_func(arg, buffer=buf, size=length)
>
> What are the names 'template' and 'length' achieving? Why should they
> claim your attention?

What is the name 'buf' in the binding expression achieving? Why should
it claim my attention?
It's not any different: it's just something that's used in a statement
then unnecessary. Yet it will persist until the end of the enclosing
scope, being retained for no reason. Perhaps we need C-like nested
scopes, if such is the concern about names that live for too long?

(of course, the fact that `my_func` needs you to pass its argument's
length as a separate argument, while it could compute it by itself, is
a bit silly)

As a side note, personally, I'm usually much more concerned about the
lifetime of *values* than the lifetime of names. The latter are cheap,
the former can represent expensive resources.

Regards

Antoine.

Yury Selivanov

unread,
Apr 25, 2018, 8:48:03 PM4/25/18
to Chris Angelico, Python-Dev
On Wed, Apr 25, 2018 at 8:22 PM Chris Angelico <ros...@gmail.com> wrote:
[..]
> > my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
> >
> > To my eye this is an anti-pattern. One line of code was saved, but the
> > other line becomes less readable. The fact that 'buf' can be used after
> > that line means that it will be harder for a reader to trace the origin
of
> > the variable, as a top-level "buf = " statement would be more visible.

> Making 'buf' more visible is ONLY a virtue if it's going to be used
> elsewhere. Otherwise, the name 'buf' is an implementation detail of
> the fact that this function wants both a buffer and a size. Should you
> want to expand this out over more lines, you could do this:

Chris, you didn't read that paragraph in my email to the end or I did a
poor job at writing it.

My point is that "buf" can still be used below that line, and therefore
sometimes it will be used, as a result of quick refactoring or poor coding
style. It's just how things happen when you write code: it gets rewritten
and parts of it left outdated or not properly revised. *If* "buf" is used
below that line it *will* be harder to find where it was initially set.

Anyways, I don't want to distract everyone further so I'm not interested
in continuing the discussion about what is readable and what is not.
My own opinion on this topic is unlikely to change. I wanted to explain
my -1; hopefully it will be noted.

Yury

Łukasz Langa

unread,
Apr 25, 2018, 9:54:21 PM4/25/18
to Chris Angelico, Python-Dev

On 25 Apr, 2018, at 5:20 PM, Chris Angelico <ros...@gmail.com> wrote:

On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
<yseliv...@gmail.com> wrote:
Just yesterday this snippet was used on python-dev to show how great the
new syntax is:

         my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))

To my eye this is an anti-pattern.  One line of code was saved, but the
other line becomes less readable.  The fact that 'buf' can be used after
that line means that it will be harder for a reader to trace the origin of
the variable, as a top-level "buf = " statement would be more visible.

Making 'buf' more visible is ONLY a virtue if it's going to be used
elsewhere. Otherwise, the name 'buf' is an implementation detail of
the fact that this function wants both a buffer and a size.

You're claiming that `:=` is nicer in this situation because it's less
prominent than regular assignment and thus doesn't suggest that the name
stays visible later.

But as others said, `:=` *does* make the name visible later until the
enclosing scope ends.  In fact, a large part of its appeal is that you
can use the result later (as in the `re.search()` example).  Will it be
visible enough to the reaser in those cases then?

There seems to be a conflict there.

The question of assignment visibility also makes me think about
unintentional name shadowing::

    buf = some_value

    ...  # 20 lines

    my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))

    ...  # 20 lines

    buf  # <-- What value does this have?


Even if we're not using the call pattern, there can be plenty of logic
tests which aren't very obvious::

    buf = some_value

    ...  # 20 lines

    if node.parent is not None and (buf := node.parent.buffer):
        ... # 10 lines

    ...  # 20 lines

    buf  # <-- What value does this have?


This is even more interesting because now `buf` isn't rebound
*always*.

So if I'm confused about an unexpected change in value of `buf`, I'll
skim the code, fail to find the assignment, and then grep for `buf =`
and also fail to find the assignment.  Yes, I could have searched for
just `buf` instead but that will give me too many false positives,
especially if I'm using a primitive text editor search or don't know
about \b in regular expressions.

Debugging this can be confusing.  I know it can since a similar
annoyance can be observed with the magic pseudo-scope of `except`::

    err = some_value
    try:
        ...
    except Error as err:
        ...

    err  # <-- now sometimes it's not defined


Just like Barry, I debugged a few cases of this in the past and within
larger functions this can be hard to find.

-- Ł

signature.asc

Wes Turner

unread,
Apr 25, 2018, 11:08:55 PM4/25/18
to Łukasz Langa, Python-Dev
Would this make it easier to put too much code on one line?

Is there a good way to get *branch coverage* stats instead of just *line coverage*?

Someone can probably explain with some tested pretty code for me why this would be necessary or helpful; why it wouldn't make line coverage stats more misleading for the sake of lazy?
 

-- Ł

Raymond Hettinger

unread,
Apr 25, 2018, 11:36:31 PM4/25/18
to Yury Selivanov, Python-Dev@Python. Org
FWIW, I concur with all of Yuri's thoughtful comments.

After re-reading all the proposed code samples, I believe that
adopting the PEP will make the language harder to teach to people
who are not already software engineers. To my eyes, the examples
give ample opportunity for being misunderstood and will create a
need to puzzle-out the intended semantics.

On the plus side, the proposal does address the occasional minor
irritant of writing an assignment on a separate line. On the minus side,
the visual texture of the new code is less appealing. The proposal
also messes with my mental model for the distinction between
expressions and statements.

It probably doesn't matter at this point (minds already seem to be made up),
but put me down for -1. This is a proposal we can all easily live without.


Raymond

Tim Peters

unread,
Apr 26, 2018, 12:42:40 AM4/26/18
to Raymond Hettinger, Python-Dev@Python. Org
[Raymond Hettinger <raymond....@gmail.com>]
> After re-reading all the proposed code samples, I believe that
> adopting the PEP will make the language harder to teach to people
> who are not already software engineers.

Can you elaborate on that? I've used dozens of languages over the
decades, most of which did have some form of embedded assignment.
Yes, I'm a software engineer, but I've always pitched in on "help
forums" too. One language feature conspicuous by absence in newbie
confusions was, consistently, assignment expressions. Read any book
or tutorial for such a language, and you'll find very little space
devoted to them too.

What's to learn? If they understand "binding a name" _at all_ (which
they must to even begin to write a non-trivial program), the only
twist is that a binding expression returns the value being bound.
Binding expressions certainly wouldn't be the _first_ thing to teach
people. But by the time it would make sense to teach them, it's hard
for me to grasp how a student could struggle with such a tiny
variation on what they've already learned (all the subtleties are in
what - exactly - "binding"means - which they already faced the first
time they saw "j = 1").


> To my eyes, the examples give ample opportunity for being
> misunderstood and will create a need to puzzle-out the intended semantics.

Some do, many don't. The same can be said of a great many constructs ;-)

> ...

Łukasz Langa

unread,
Apr 26, 2018, 1:15:59 AM4/26/18
to Tim Peters, Raymond Hettinger, Python-Dev@Python. Org
[Uncle T]
One language feature conspicuous by absence in newbie
confusions was, consistently, assignment expressions.  Read any book
or tutorial for such a language, and you'll find very little space
devoted to them too.

Well, you have an entire code style built around this feature called Yoda conditions. You teach people on Day 1 to never ever confuse == with =. Some compilers even warn about this because so many people did it wrong.


What's to learn?  If they understand "binding a name" _at all_ (which
they must to even begin to write a non-trivial program), the only
twist is that a binding expression returns the value being bound.

Ha, not in Python! Here we have *different syntax* for assignments in expressions. Well, you can also use it as a statement. But don't! We have a better one for that. And that one supports type annotations, can unpack and assign to many targets at the same time, and can even increment, multiply and so on, at once. But the other one can't.

So only use the Pascal one in expressions. But don't forget parentheses, otherwise it will bind the thing you probably didn't want anyway.

To my eyes, the examples give ample opportunity for being
misunderstood and will create a need to puzzle-out the intended semantics.

Some do, many don't.

As soon as we have to wrap a part of an expression in parentheses, parsing the entire thing becomes more complex. Often enough it will cause the expression to exceed whatever line length limit the codebase pledged not to exceed, causing one line to become three. And again, making it trickier for a regular Łukasz to understand what's going on.

-- Ł

Greg Ewing

unread,
Apr 26, 2018, 1:24:45 AM4/26/18
to Python-Dev@Python. Org
Łukasz Langa wrote:
> What was its own assignment before
> now is part of the logic test. This saves on vertical whitespace but makes
> parsing and understanding logic tests harder.

Another way to say this is that expressions are no longer
restricted to being trees, but can be general DAGs, which
require more mental effort to understand.

Hmmm, maybe they should be called "spaghetti expressions". :-)

--
Greg

Steven D'Aprano

unread,
Apr 26, 2018, 1:36:05 AM4/26/18
to pytho...@python.org
On Thu, Apr 26, 2018 at 05:22:58PM +1200, Greg Ewing wrote:
> Łukasz Langa wrote:
> >What was its own assignment before
> >now is part of the logic test. This saves on vertical whitespace but makes
> >parsing and understanding logic tests harder.
>
> Another way to say this is that expressions are no longer
> restricted to being trees, but can be general DAGs, which
> require more mental effort to understand.

Is that right? I presume you mean that there can be cycles in
expressions involving binding-expressions. If not, what do you mean?

Can you give an example of a Python expression, involving PEP 572
binding-expressions, that is not a tree but a more general DAG or that
contains cycles?

--
Steve

Chris Angelico

unread,
Apr 26, 2018, 1:40:40 AM4/26/18
to python-dev
On Thu, Apr 26, 2018 at 3:34 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Thu, Apr 26, 2018 at 05:22:58PM +1200, Greg Ewing wrote:
>> Łukasz Langa wrote:
>> >What was its own assignment before
>> >now is part of the logic test. This saves on vertical whitespace but makes
>> >parsing and understanding logic tests harder.
>>
>> Another way to say this is that expressions are no longer
>> restricted to being trees, but can be general DAGs, which
>> require more mental effort to understand.
>
> Is that right? I presume you mean that there can be cycles in
> expressions involving binding-expressions. If not, what do you mean?
>
> Can you give an example of a Python expression, involving PEP 572
> binding-expressions, that is not a tree but a more general DAG or that
> contains cycles?

A DAG is a directed *acyclic* graph, so it still can't contain cycles.
But I have no idea what kind of expression isn't a tree as a
consequence of having an assignment in it.

ChrisA

Raymond Hettinger

unread,
Apr 26, 2018, 1:52:10 AM4/26/18
to Tim Peters, Python-Dev@Python. Org

> On Apr 26, 2018, at 12:40 AM, Tim Peters <tim.p...@gmail.com> wrote:
>
> [Raymond Hettinger <raymond....@gmail.com>]
>> After re-reading all the proposed code samples, I believe that
>> adopting the PEP will make the language harder to teach to people
>> who are not already software engineers.
>
> Can you elaborate on that?

Just distinguishing between =, :=, and == will be a forever recurring
discussion, far more of a source of confusion than the occasional
question of why Python doesn't have embedded assignment.

Also, it is of concern that a number of prominent core dev
respondents to this thread have reported difficulty scanning
the posted code samples.

> I've used dozens of languages over the
> decades, most of which did have some form of embedded assignment.

Python is special, in part, because it is not one of those languages.
It has virtues that make it suitable even for elementary school children.
We can show well-written Python code to non-computer folks and walk
them through what it does without their brains melting (something I can't
do with many of the other languages I've used). There is a virtue
in encouraging simple statements that read like English sentences
organized into English-like paragraphs, presenting itself like
"executable pseudocode".

Perl does it or C++ does it is unpersuasive. Its omission from Python
was always something that I thought Guido had left-out on purpose,
intentionally stepping away from constructs that would be of help
in an obfuscated Python contest.


> Yes, I'm a software engineer, but I've always pitched in on "help
> forums" too.

That's not really the same. I've taught Python to many thousands
of professionals, almost every week for over six years. That's given
me a keen sense of what is hard to teach. It's okay to not agree
with my assessment, but I would like for fruits of my experience
to not be dismissed in a single wisp of a sentence. Any one feature
in isolation is usually easy to explain, but showing how to combine
them into readable, expressive code is another matter. And as
Yuri aptly noted, we spend more time reading code than writing code.
If some fraction of our users finds the code harder to scan
because the new syntax, then it would be a net loss for the language.

I hesitated to join this thread because you and Guido seemed to be
pushing back so hard against anyone's who design instincts didn't favor
the new syntax. It would be nice to find some common ground and
perhaps stipulate that the grammar would grow in complexity, that a new
operator would add to the current zoo of operators, that the visual texture
of the language would change (and in a way that some including me
do not find pleasing), and that while simplest cases may afford
a small net win, it is a certitude that the syntax will routinely be
pushed beyond our comfort zone.

While the regex conditional example looks like a win, it is very modest win
and IMHO not worth the overall net increase language complexity.

Like Yuri, I'll drop-out now. Hopefully, you all wind find some value
in what I had to contribute to the conversation.


Raymond

Tim Peters

unread,
Apr 26, 2018, 1:59:51 AM4/26/18
to Łukasz Langa, Raymond Hettinger, Python-Dev@Python. Org
[Tim]

>> One language feature conspicuous by absence in newbie
>> confusions was, consistently, assignment expressions. Read any book
>> or tutorial for such a language, and you'll find very little space
>> devoted to them too.

[Łukasz Langa <luk...@langa.pl>]


> Well, you have an entire code style built around this feature called Yoda
> conditions. You teach people on Day 1 to never ever confuse == with =. Some
> compilers even warn about this because so many people did it wrong.

Sorry, I couldn't follow that. In languages like C that use easily
confused operator symbols, sure, people are forever typing "=" when
they mean "==". That's nothing to do with whether they _understand_
what the different operators do, though. They do. In languages like
Icon (that use "=" for numeric comparison and ":=" for assignment),
that never occurs. But I'm not sure that addressed the point you were
making.


>> What's to learn? If they understand "binding a name" _at all_ (which
>> they must to even begin to write a non-trivial program), the only
>> twist is that a binding expression returns the value being bound.

> Ha, not in Python! Here we have *different syntax* for assignments in
> expressions.

Yes, binding expressions in the current PEP support an extremely
limited subset of what Python's assignment statements support. That
they use different operator symbols is irrelevant to that the meaning
of "binding a name" is exactly the same for both.. _That's_ the "hard
part" to learn.


> Well, you can also use it as a statement. But don't!

Why not? _Every_ expression in Python can be used as a statement.
Nothing forbids it, and that's even (very!) useful at an interactive
prompt.


> We have a better one for that.

As a matter of style, sure, it's best to use the simplest thing that
works. As a statement in a program (as opposed to typed at a shell),
"a := 3" has the unnecessary (in that context) property of returning
(and discarding 3), so it's better style to use "a = 3" in that
context.


> And that one supports type annotations, can unpack and assign to many
> targets at the same time, and can even increment, multiply and so on, at once.
> But the other one can't.

So? math.sqrt() blows up when passed -1, but cmath.sqrt() doesn't.
Different tools for different tasks.


> So only use the Pascal one in expressions. But don't forget parentheses,
> otherwise it will bind the thing you probably didn't want anyway.

[Raymond]


>>> To my eyes, the examples give ample opportunity for being
>>> misunderstood and will create a need to puzzle-out the intended
>>> semantics.

>> Some do, many don't.

> As soon as we have to wrap a part of an expression in parentheses, parsing
> the entire thing becomes more complex. Often enough it will cause the
> expression to exceed whatever line length limit the codebase pledged not to
> exceed, causing one line to become three. And again, making it trickier for
> a regular Łukasz to understand what's going on.

At this point I think you must have a lower opinion of Python
programmers than I have ;-) If adding even a dozen characters to a
line makes it exceed a reasonable line-length guide, the code was
almost certainly too confusingly dense to begin with. All the
binding-expression examples I've given as "improvements" had _oceans_
of horizontal white space to swim in.

Guido's if/elif/elif/elif/ ... complex text-processing example didn't,
but because the current lack of an ability to bind-and-test in one
gulp forced the `elif` parts to be ever-more-deeply-indented `if`
blocks instead.

So, to match your sarcasm, here's mine: try using a feature for what
it's good at instead of for what it's bad at ;-)

Steven D'Aprano

unread,
Apr 26, 2018, 2:11:54 AM4/26/18
to pytho...@python.org
On Wed, Apr 25, 2018 at 10:14:11PM -0700, Łukasz Langa wrote:

> So only use the Pascal one in expressions. But don't forget
> parentheses, otherwise it will bind the thing you probably didn't want
> anyway.

Binding expressions are no worse than any other expression: sometimes
you need to bracket terms to change the default precedence, and
sometimes you don't.

And sometimes, even if we don't *need* parens, we use them anyway
because it makes the expression easier to read and understand.

Unless you have a language with no operator precedence at all, a purely
left-to-right evaluation order like Forth or (I think?) APL, there will
always be circumstances where parens are needed. Criticising binding-
expressions for that reason, especially implying that we must always use
parens, is simply FUD.


[...]


> As soon as we have to wrap a part of an expression in parentheses,
> parsing the entire thing becomes more complex.

Unless it becomes less complex to read and understand.

I for one always have difficulty parsing complex boolean tests unless I
bracket some or all of the parts, even when they're not strictly needed.
Consequently I try very hard not to write complex bool tests in the
first place, but when I can't avoid it, a few extra brackets really
helps simplify the logic.


> Often enough it will
> cause the expression to exceed whatever line length limit the codebase
> pledged not to exceed, causing one line to become three.

Just how often are your lines within two characters of the maximum
column so that adding a pair of brackets () will "often enough" put it
over the limit? Seems pretty unlikely to me. This sounds really like
picking at straws.


--
Steve

Greg Ewing

unread,
Apr 26, 2018, 2:34:27 AM4/26/18
to Python-Dev
> On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
> <yseliv...@gmail.com> wrote:
>
>> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))

Obviously what we want here is a variant of the binding
expression that also makes it a keyword argument:

my_func(arg, buffer ::= [None]*get_size(), size = len(buffer))

--
Greg

Greg Ewing

unread,
Apr 26, 2018, 2:54:18 AM4/26/18
to Python-Dev@Python. Org
Łukasz Langa wrote:
> Ha, not in Python! Here we have *different syntax* for assignments in
> expressions. Well, you can also use it as a statement. But don't!

This is what I least like about the proposal. We should
be moving in the direction of removing warts, but here
we would be *creating* one (two different assignment
operators with overlapping use cases) that we won't be
able to get rid of without a Python 4000 (that Guido has
promised won't happen).

--
Greg

Tim Peters

unread,
Apr 26, 2018, 3:02:53 AM4/26/18
to Raymond Hettinger, Python-Dev@Python. Org
[Raymond Hettinger <raymond....@gmail.com>]
>>> After re-reading all the proposed code samples, I believe that
>>> adopting the PEP will make the language harder to teach to people
>>> who are not already software engineers.

[Tim]
>> Can you elaborate on that?

[Raymond]
> Just distinguishing between =, :=, and == will be a forever recurring
> discussion, far more of a source of confusion than the occasional
> question of why Python doesn't have embedded assignment.

To be clear, is distinguishing between "=" and "==" already a forever
recurring discussion in your experience? Or are you predicting that
adding ":=" will create that situation?


> Also, it is of concern that a number of prominent core dev
> respondents to this thread have reported difficulty scanning
> the posted code samples.

Yes, it is - although some of the examples sucked ;-)


>> I've used dozens of languages over the
>> decades, most of which did have some form of embedded assignment.

> Python is special, in part, because it is not one of those languages.
> It has virtues that make it suitable even for elementary school children.
> We can show well-written Python code to non-computer folks and walk
> them through what it does without their brains melting (something I can't
> do with many of the other languages I've used). There is a virtue
> in encouraging simple statements that read like English sentences
> organized into English-like paragraphs, presenting itself like
> "executable pseudocode".

It's certainly possible to stick to a subset of Python for which
that's true. But I didn't mention those dozens of languages because I
seek to emulate them, but to establish that I've had decades of
experience with embedded assignments in a wide variety of languages
and language communities.


> Perl does it or C++ does it is unpersuasive.

Wasn't meant to be.


> Its omission from Python was always something that I thought Guido had
> left-out on purpose, intentionally stepping away from constructs that would
> be of help in an obfuscated Python contest.

He left out lots of stuff at first, but warmed to it later. Probably
the most profound: there were exactly and only 3 scopes at first:
local, global, and builtin. Functions (for example) could still nest,
but had no way to access names local to enclosing functions save via
deep trickery. That was a noble experiment (it was a deliberate
attempt to avoid complex scoping rules), but eventually proved too
restrictive in practice.

This is nothing compared to that ;-) But it's a tiny bit related in
that biting the arbitrarily-deeply-nested-scopes bullet was aimed more
at experienced programmers than at newbies. The scoping rules became
far harder to explain as a result - but far more what experienced
programmers expected.


>> Yes, I'm a software engineer, but I've always pitched in on "help
>> forums" too.

> That's not really the same.

I believe it!

> I've taught Python to many thousands of professionals, almost
> every week for over six years. That's given me a keen sense of
> what is hard to teach. It's okay to not agree with my assessment,
> but I would like for fruits of my experience to not be dismissed in a
> single wisp of a sentence.

I asked you to elaborate - I didn't dismiss anything. You merely made
a raw assertion in your original message, without enough detail to
even know _what_ it is you thought would be hard to teach. Your
elaboration is helping.

> Any one feature in isolation is usually easy to explain, but showing
> how to combine them into readable, expressive code is another matter.

OK, so it's not binding expressions in isolation that you expect will
be hard to teach if they're added, but ... how to use them
intelligently (if ever)? That's progress, if so.

That part I can see having major trouble with. Even the proponents of
this PEP don't always agree with each other about which examples are
"good ones".


> And as Yuri aptly noted, we spend more time reading code than writing code.
> If some fraction of our users finds the code harder to scan
> because the new syntax, then it would be a net loss for the language.

It would be a tradeoff pitting their losses against others' gains, of
course. I don't know how to quantify that (not even to the extent of
determining the sign bit) in advance. I'm also at least as concerned
about - indeed - professional software engineers as beginners.


> I hesitated to join this thread because you and Guido seemed to be
> pushing back so hard against anyone's who design instincts didn't favor
> the new syntax.

That's just vigorous debate, at least on my part. Guido gets annoyed
by emotional tirades and FUD, of which there's always plenty in
threads that have gone on for hundreds of messages (I don't know
whether you followed any of this on python-ideas, but most arguments
on python-dev were already many-times-over old by the time it first
appeared here).


> It would be nice to find some common ground and perhaps stipulate that the
> grammar would grow in complexity, that a new operator would add to the
> current zoo of operators, that the visual texture of the language would change
> (and in a way that some including me do not find pleasing), and that while
> simplest cases may afford a small net win, it is a certitude that the syntax
> will routinely be pushed beyond our comfort zone.
>
> While the regex conditional example looks like a win, it is very modest win
> and IMHO not worth the overall net increase language complexity.
>
> Like Yuri, I'll drop-out now. Hopefully, you all wind find some value
> in what I had to contribute to the conversation.

Absolutely! While I've slowly moved from -1 to +1 on this one, I
respect your -1, and am grateful for your thoughtful elaboration.
Indeed, I'll feel better now when Guido rejects it ;-)

Greg Ewing

unread,
Apr 26, 2018, 3:18:16 AM4/26/18
to Python-Dev@Python. Org
Tim Peters wrote:
> As a statement in a program (as opposed to typed at a shell),
> "a := 3" has the unnecessary (in that context) property of returning
> (and discarding 3), so it's better style to use "a = 3" in that
> context.

That seems like a post-hoc justification. If := were the one
and only assignment symbol, the compiler could easily optimise
away the extra DUP_TOP or whatever is involved.

--
Greg

Greg Ewing

unread,
Apr 26, 2018, 3:26:54 AM4/26/18
to Python-Dev@Python. Org
Tim Peters wrote:
> To my eyes, this is genuinely harder to follow, despite its relative brevity:
>
> while total != (total := total + term):

Not surprising, since there are at least two deeper levels
of subtlety at play:

1. total isn't just naming a subexpression, it's being
rebound to something that depends on its previous value.

2. Order of evaluation is being relied on to ensure that
the new value of total is compared to its old value.

Steven D'Aprano

unread,
Apr 26, 2018, 3:31:04 AM4/26/18
to pytho...@python.org
On Thu, Apr 26, 2018 at 07:16:28PM +1200, Greg Ewing wrote:
> Tim Peters wrote:
> >As a statement in a program (as opposed to typed at a shell),
> >"a := 3" has the unnecessary (in that context) property of returning
> >(and discarding 3), so it's better style to use "a = 3" in that
> >context.
>
> That seems like a post-hoc justification. If := were the one
> and only assignment symbol, the compiler could easily optimise
> away the extra DUP_TOP or whatever is involved.

Its still bad form to rely on compiler-dependent optimizations which
aren't part of the language specification.

And to steal an earlier idea from Tim, it is especially unfortunate if
you copy

data := sorted(huge_list_with_billions_of_items)

from a program and paste it into your REPL, then can't type again for an
hour or two.

The longer I think about this, the more I am convinced that having two
forms of assignment, one a statement with no return value and the other
an expression with a return value, is a feature, not a wart or bug. Yes,
it does add some complexity to the language, but it is *useful*
complexity. If all complexity was bad, we'd still be programming by
flicking toggle switches.



--
Steve

Terry Reedy

unread,
Apr 26, 2018, 3:33:11 AM4/26/18
to pytho...@python.org
On 4/25/2018 8:20 PM, Chris Angelico wrote:
> On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
> <yseliv...@gmail.com> wrote:
>> Just yesterday this snippet was used on python-dev to show how great the
>> new syntax is:
>>
>> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))

What strikes me as awful about this example is that len(buf) is
get_size(), so the wrong value is being named and saved.
'size=len(buf)' is, in a sense, backwards.

buflen = get_size()
my_func(arg, buffer = [None]*buflen, size=buflen)

Is standard, clear Python code. I do not see that
my_func(arg, buffer=[None]*(buflen:=get_size()), size=buflen)
is an improvement.

--
Terry Jan Reedy

Steven D'Aprano

unread,
Apr 26, 2018, 3:58:15 AM4/26/18
to pytho...@python.org
On Thu, Apr 26, 2018 at 03:31:13AM -0400, Terry Reedy wrote:
> On 4/25/2018 8:20 PM, Chris Angelico wrote:
> >On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
> ><yseliv...@gmail.com> wrote:
> >>Just yesterday this snippet was used on python-dev to show how great the
> >>new syntax is:
> >>
> >> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
>
> What strikes me as awful about this example is that len(buf) is
> get_size(), so the wrong value is being named and saved.
> 'size=len(buf)' is, in a sense, backwards.

Terry is absolutely right, and I'm to blame for that atrocity. Mea
culpa.

But Yury is misrepresenting the context in which I came up with that
snippet. It was not "to show how great the new syntax is", but to try to
give a *more realistic example of what we might right, instead of the
toy example he had given:

Yuri claimed that

my_func(a=(b:=foo))

was "barely readable" and I responded:


There's no advantage to using binding-expressions unless
you're going to re-use the name you just defined, and that
re-use will give you a hint as to what is happening:

Alas, my spur of the moment example was crap, as you point out, but the
point still stands: Yuri's example is mysterious, because there's a
local variable b assigned to which doesn't seem to be used anywhere. It
is either a mistake of some sort, simply poor code, or maybe b is used
somewhere else in the function. Which seems poor style: if b is intended
to be used elsewhere in the function, why not use normal assignment?

Using a binding expression is a hint that it is likely *intended* to
only be used in the current expression, or block. That's not a rule
that the compiler ought to enforce, but it is a reasonable stylistic
idiom, like using ALLCAPS for constants. At least, that's my opinion.

So, crappy example or not, if I see a binding expression, that hints
that the name used is needed in the current expression multiple times.
It certainly should motivate me to look further ahead in the current
expression to see where the newly defined variable is used next, and if
it is only used once, to wonder if there has been some mistake.

Whereas a stand alone assignment doesn't really give any hint (apart
from vertical proximity, which is a very poor hint) as to how often and
where a variable is used.


--
Steve

Antoine Pitrou

unread,
Apr 26, 2018, 4:19:25 AM4/26/18
to pytho...@python.org
On Wed, 25 Apr 2018 18:52:34 -0700
Łukasz Langa <luk...@langa.pl> wrote:
> > On 25 Apr, 2018, at 5:20 PM, Chris Angelico <ros...@gmail.com> wrote:
> >
> > On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
> > <yseliv...@gmail.com> wrote:
> >> Just yesterday this snippet was used on python-dev to show how great the
> >> new syntax is:
> >>
> >> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
> >>
> >> To my eye this is an anti-pattern. One line of code was saved, but the
> >> other line becomes less readable. The fact that 'buf' can be used after
> >> that line means that it will be harder for a reader to trace the origin of
> >> the variable, as a top-level "buf = " statement would be more visible.
> >
> > Making 'buf' more visible is ONLY a virtue if it's going to be used
> > elsewhere. Otherwise, the name 'buf' is an implementation detail of
> > the fact that this function wants both a buffer and a size.
>
> You're claiming that `:=` is nicer in this situation because it's less
> prominent than regular assignment and thus doesn't suggest that the name
> stays visible later.
>
> But as others said, `:=` *does* make the name visible later until the
> enclosing scope ends. In fact, a large part of its appeal is that you
> can use the result later (as in the `re.search()` example). Will it be
> visible enough to the reaser in those cases then?
>
> There seems to be a conflict there.

Not only, but seeing `:=` hints that something *special* is going on
(some inner expression is being bound to a name). So now we have to be
extra careful when reading and reviewing code written that people who
like using that syntactical feature.

I also wonder how long it will be before someone writes:

def f(arg):
global _lazy_value
if predicate(arg) and (_lazy_value := frobnicate()) > arg:
...

(or something similar with "nonlocal")

Regards

Antoine.

Antoine Pitrou

unread,
Apr 26, 2018, 4:32:50 AM4/26/18
to pytho...@python.org
On Thu, 26 Apr 2018 17:29:17 +1000
Steven D'Aprano <st...@pearwood.info> wrote:
> On Thu, Apr 26, 2018 at 07:16:28PM +1200, Greg Ewing wrote:
> > Tim Peters wrote:
> > >As a statement in a program (as opposed to typed at a shell),
> > >"a := 3" has the unnecessary (in that context) property of returning
> > >(and discarding 3), so it's better style to use "a = 3" in that
> > >context.
> >
> > That seems like a post-hoc justification. If := were the one
> > and only assignment symbol, the compiler could easily optimise
> > away the extra DUP_TOP or whatever is involved.
>
> Its still bad form to rely on compiler-dependent optimizations which
> aren't part of the language specification.

If such were the need, you could very well make it part of the language
specification. We are talking about a trivial optimization that any
runtime could easily implement (e.g. if a sequence `DUP_TOP, STORE_FAST,
POP_TOP` occurs, replace it with `STORE_FAST`).

Any runtime already has to implement a set of performance properties
that's far less trivial than that. For example, any decent runtime is
expected to provide amortized O(1) list append or dict insertion. You
are breaking user expectations if you don't.

> And to steal an earlier idea from Tim, it is especially unfortunate if
> you copy
>
> data := sorted(huge_list_with_billions_of_items)
>
> from a program and paste it into your REPL, then can't type again for an
> hour or two.

Well, how do languages where assignment is an expression returning the
assigned value make their REPLs work? I'm sure they don't inflict that
on their users, so it's certainly a solvable problem.

(abstractly: if the user is executing a top-level assignment expression,
don't display the assignment result)

> The longer I think about this, the more I am convinced that having two
> forms of assignment, one a statement with no return value and the other
> an expression with a return value, is a feature, not a wart or bug.

Yet, curiously, no other language seems to replicate that "feature" :-)

It's nice to innovate, but being the only one to do something may very
well mean that you're doing something ridiculous.

Regards

Antoine.

Antoine Pitrou

unread,
Apr 26, 2018, 4:38:04 AM4/26/18
to pytho...@python.org
On Thu, 26 Apr 2018 15:34:17 +1000

Steven D'Aprano <st...@pearwood.info> wrote:

> On Thu, Apr 26, 2018 at 05:22:58PM +1200, Greg Ewing wrote:
> > Łukasz Langa wrote:
> > >What was its own assignment before
> > >now is part of the logic test. This saves on vertical whitespace but makes
> > >parsing and understanding logic tests harder.
> >
> > Another way to say this is that expressions are no longer
> > restricted to being trees, but can be general DAGs, which
> > require more mental effort to understand.
>
> Is that right? I presume you mean that there can be cycles in
> expressions involving binding-expressions. If not, what do you mean?
>
> Can you give an example of a Python expression, involving PEP 572
> binding-expressions, that is not a tree but a more general DAG or that
> contains cycles?

Depends if you mean a graph between names or values?

If between names, you can even have cycles AFAICT:

((a: = a + b), (b: = a))

Regards

Antoine.

Chris Angelico

unread,
Apr 26, 2018, 5:20:49 AM4/26/18
to python-dev
On Thu, Apr 26, 2018 at 6:30 PM, Antoine Pitrou <soli...@pitrou.net> wrote:
> On Thu, 26 Apr 2018 17:29:17 +1000
> Steven D'Aprano <st...@pearwood.info> wrote:
>> On Thu, Apr 26, 2018 at 07:16:28PM +1200, Greg Ewing wrote:
>> > Tim Peters wrote:
>> > >As a statement in a program (as opposed to typed at a shell),
>> > >"a := 3" has the unnecessary (in that context) property of returning
>> > >(and discarding 3), so it's better style to use "a = 3" in that
>> > >context.
>> >
>> > That seems like a post-hoc justification. If := were the one
>> > and only assignment symbol, the compiler could easily optimise
>> > away the extra DUP_TOP or whatever is involved.
>>
>> Its still bad form to rely on compiler-dependent optimizations which
>> aren't part of the language specification.
>
> If such were the need, you could very well make it part of the language
> specification. We are talking about a trivial optimization that any
> runtime could easily implement (e.g. if a sequence `DUP_TOP, STORE_FAST,
> POP_TOP` occurs, replace it with `STORE_FAST`).

Not at the REPL, no. At the REPL, you need to actually print out that
value. It's semantically different.

> Any runtime already has to implement a set of performance properties
> that's far less trivial than that. For example, any decent runtime is
> expected to provide amortized O(1) list append or dict insertion. You
> are breaking user expectations if you don't.

You assume that, but it isn't always the case. Did you know, for
instance, that string subscripting (an O(1) operation in CPython) is
allowed to be O(n) in other Python implementations?

Now you do.

ChrisA

Antoine Pitrou

unread,
Apr 26, 2018, 5:39:58 AM4/26/18
to pytho...@python.org
On Thu, 26 Apr 2018 19:19:05 +1000
Chris Angelico <ros...@gmail.com> wrote:
> >
> > If such were the need, you could very well make it part of the language
> > specification. We are talking about a trivial optimization that any
> > runtime could easily implement (e.g. if a sequence `DUP_TOP, STORE_FAST,
> > POP_TOP` occurs, replace it with `STORE_FAST`).
>
> Not at the REPL, no. At the REPL, you need to actually print out that
> value. It's semantically different.

The REPL compiles expressions in a different mode than regular modules,
so that's entirely a strawman. The REPL doesn't care that it will
spend a fraction of microseconds executing two additional bytecodes
before presenting something to the user.

> > Any runtime already has to implement a set of performance properties
> > that's far less trivial than that. For example, any decent runtime is
> > expected to provide amortized O(1) list append or dict insertion. You
> > are breaking user expectations if you don't.
>
> You assume that, but it isn't always the case.

Yeah, so what?

> Did you know, for instance, that string subscripting (an O(1) operation in CPython) is
> allowed to be O(n) in other Python implementations?

Why would I give a sh*t? Here, we are not talking about a non-trivial
design decision such as how to represent string values internally
(utf-8 vs. fixed-width, etc.). We are talking about optimizing a
trivial bytecode sequence into another more trivial bytecode sequence.
CPython can easily do it. PyPy can easily do it. Other runtimes can
easily do it.

If some implementation is significantly slower because it can't
optimize away pointless DUP_TOPs *and* it implements DUP_TOP
inefficiently enough to have user-noticeable effect, then it's either
1) a deliberate toy, a proof-of-concept not meant for serious use, or
2) a pile of crap produced by incompetent people.

So there's zero reason to bother about efficiency issues here.

Regards

Antoine.

Steve Holden

unread,
Apr 26, 2018, 6:01:36 AM4/26/18
to Łukasz Langa, Python-Dev@Python. Org
On Wed, Apr 25, 2018 at 9:55 PM, Łukasz Langa <luk...@langa.pl> wrote:

> On 25 Apr, 2018, at 1:28 PM, Guido van Rossum <gu...@python.org> wrote:
>
> You don't seem to grasp the usability improvements this will give. I hear you but at this point appeals to Python's "Zen" don't help you.

This reads dismissive to me. I did read the PEP and followed the discussion on
python-dev. I referred to PEP 20 because it distills what's unique about the
value proposition of Python. It's our shared vocabulary.

​Perhaps so, but no PEP is chiselled in stone, and I would suggest that PEP 20 is the least authoritative from a didactic point of view.
 
Can you address the specific criticism I had? To paraphrase it without PEP 20
jargon:

> (name := expression) makes code less uniform.  It inserts more information
> into a place that is already heavily packed with information (logic tests).


​One could argue the same about list comprehensions if one chose: they make code denser (by expressing the same algorithm in a shorter spelling). I'm not sure what you mean by "less uniform."​

Steve Holden

unread,
Apr 26, 2018, 6:22:18 AM4/26/18
to Steven D'Aprano, Python-Dev@Python. Org
On Thu, Apr 26, 2018 at 8:56 AM, Steven D'Aprano <st...@pearwood.info> wrote:
On Thu, Apr 26, 2018 at 03:31:13AM -0400, Terry Reedy wrote:
> On 4/25/2018 8:20 PM, Chris Angelico wrote:
> >On Thu, Apr 26, 2018 at 10:11 AM, Yury Selivanov
> ><yseliv...@gmail.com> wrote:
> >>Just yesterday this snippet was used on python-dev to show how great the
> >>new syntax is:
> >>
> >>           my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
>
> What strikes me as awful about this example is that len(buf) is
> get_size(), so the wrong value is being named and saved.
> 'size=len(buf)' is, in a sense, backwards.

Terry is absolutely right, and I'm to blame for that atrocity. Mea
culpa.

​Perhaps a better spelling would be

    my_func(arg, buffer=[None]*(buflen := get_size()), size=buflen)

​[...]

Greg Ewing

unread,
Apr 26, 2018, 6:36:17 AM4/26/18
to pytho...@python.org
Antoine Pitrou wrote:
> Well, how do languages where assignment is an expression returning the
> assigned value make their REPLs work? I'm sure they don't inflict that
> on their users, so it's certainly a solvable problem.

I can't think of any such language that has a REPL
offhand, but here's a possible solution:


x := expr # doesn't print anything

(x := expr) # prints the result

I.e. special-case a stand-alone assignment, but
allow overriding that with parens if needed.

--
Greg

Greg Ewing

unread,
Apr 26, 2018, 6:41:18 AM4/26/18
to pytho...@python.org
Antoine Pitrou wrote:
> Depends if you mean a graph between names or values?
>
> If between names, you can even have cycles AFAICT:
>
> ((a: = a + b), (b: = a))

I was thinking more of the dataflow graph. That's not
a cycle between *values*, since the new values being
bound are calculated from the previous values of the
names.

--
Greg

Stephen J. Turnbull

unread,
Apr 26, 2018, 7:29:36 AM4/26/18
to Łukasz Langa, Python-Dev
Łukasz Langa writes:
> > On 25 Apr, 2018, at 5:20 PM, Chris Angelico <ros...@gmail.com> wrote:

> You're claiming that `:=` is nicer in this situation because it's less
> prominent than regular assignment and thus doesn't suggest that the name
> stays visible later.

FWIW, I read what he wrote as "*assuming* buf is not going to be used
later", and thus took a more nuanced idea from it: Use a separate
statement when you do use it later, and use a binding expression when
its scope is *in fact* only that line.

BTW, I don't find the "it could be misused, so it will be misused"
argument persuasive. I agree it's true, but don't think it matters,
per the "consenting adults" principle, since binding expressions have
other, important, use cases.

We could add a statement to PEP 8 mildly deprecating this particular
use. How about this:

In some examples, the binding expression is used in function
arguments where one argument depends on an earlier one, such as

foo(buffer=(buf := [None]*get_size()), size=len(buf))

In examples like this one, it is preferable where possible to
refactor the function to calculate the dependent variable itself,
or to use an assignment statement to define the bound variable.
The latter style is *strongly* preferred when the bound variable
will be used later in the scope.

Steve

David Mertz

unread,
Apr 26, 2018, 7:51:00 AM4/26/18
to Tim Peters, Raymond Hettinger, Python-Dev
[Raymond Hettinger <raymond....@gmail.com>]

> Python is special, in part, because it is not one of those languages.
> It has virtues that make it suitable even for elementary school children.
> We can show well-written Python code to non-computer folks and walk
> them through what it does without their brains melting (something I can't
> do with many of the other languages I've used).  There is a virtue
> in encouraging simple statements that read like English sentences
> organized into English-like paragraphs, presenting itself like
> "executable pseudocode".

While this is true and good for most Python code, can you honestly explain asyncio code with async/await to these non-programmers?! What about the interfaces between async and synchronous portions?

I've been programming for 40 years, in Python for 20 of them. I cannot read any block of async code without thinking VERY SLOWLY about what's going on, then getting it wrong half the time. I even teach Python almost as much as Raymond does.

There's a certain hand-waving approach to teaching async/await where you say not to worry about those keywords, and just assume the blocks are coordinated "somehow, behind the scenes." That's not awful for reading *working* code, but doesn't let you write it.

I'm not saying binding expressions are likewise reserved for a special but important style of programming. If included, I expect them to occur more-or-less anywhere. So Raymond's concern about teachability is more pressing (I've only taught async twice, and I know Raymond's standard course doesn't do it, all the other code is unaffected by that unused 'await' lurking in the syntax). Still, there are good reasons why not all Python code is aimed at non-computer folks.

Steven D'Aprano

unread,
Apr 26, 2018, 8:11:44 AM4/26/18
to pytho...@python.org
On Thu, Apr 26, 2018 at 10:17:34AM +0200, Antoine Pitrou wrote:

> I also wonder how long it will be before someone writes:
>
> def f(arg):
> global _lazy_value
> if predicate(arg) and (_lazy_value := frobnicate()) > arg:
> ...
>
> (or something similar with "nonlocal")

What if they did? I don't think that's especially worse than any other
use of a global, including the current version:

def f(arg):
global _lazy_value
_lazy_value = frobnicate()
if predicate(arg) and _lazy_value > arg:
...


I'm not putting either version forward as paragons of Pythonic code, but
honestly, they're not so awful that we should reject this PEP because of
the risk that somebody will do this. People write crappy code and misuse
features all the time. I cannot count the number of times people write
list comps just for the side-effects, intentionally throwing away the
resulting list:

[alist.sort() for alist in list_of_lists]
[print(alist) for alist in list_of_lists]

and the honest truth is that when I'm engaged in exploratory coding in
the REPR, sometimes if I'm feeling lazy I'll do it too. The world goes
on, and comprehensions are still useful even if people sometimes misuse
them.



--
Steve

Steven D'Aprano

unread,
Apr 26, 2018, 8:33:41 AM4/26/18
to pytho...@python.org
On Thu, Apr 26, 2018 at 10:34:29PM +1200, Greg Ewing wrote:
> Antoine Pitrou wrote:
> >Well, how do languages where assignment is an expression returning the
> >assigned value make their REPLs work? I'm sure they don't inflict that
> >on their users, so it's certainly a solvable problem.
>
> I can't think of any such language that has a REPL
> offhand, but here's a possible solution:


Here's the Rhino Javascript REPL:

[steve@ando ~]$ rhino
Rhino 1.7 release 0.7.r2.3.el5_6 2011 05 04
js> x = (a = 99) + 1
100


Here's the standard Ruby REPL:

[steve@ando ~]$ irb
irb(main):001:0> x = (a = 99) + 1
=> 100


So both of these REPLs do print the result of the expression.

R, on the other hand, doesn't print the results of assignment
expressions:

> x <- (a <- 99) + 1
> c(x, a)
[1] 100 99

Rhino, however, does suppress printing if you prefix the variable with
"var" or "const".



--
Steve

David Mertz

unread,
Apr 26, 2018, 9:11:26 AM4/26/18
to Tim Peters, Raymond Hettinger, Python-Dev@Python. Org
FWIW, the combination of limiting the PEP to binding expressions and the motivating example of sequential if/elif tests that each need to utilize an expression in their body (e.g. matching various regexen by narrowing, avoiding repeated indent) gets me to +1.

I still think the edge case changes to comprehension semantics is needless for this PEP. However, it concerns a situation I don't think I've ever encountered in the wild, and certainly never relied on the old admittedly odd behavior.

On Thu, Apr 26, 2018, 2:01 AM Tim Peters <tim.p...@gmail.com> wrote:
Yes, binding expressions in the current PEP support an extremely
limited subset of what Python's assignment statements support.[...]

Nick Coghlan

unread,
Apr 26, 2018, 10:33:43 AM4/26/18
to Chris Angelico, python-dev
On 26 April 2018 at 15:38, Chris Angelico <ros...@gmail.com> wrote:
> On Thu, Apr 26, 2018 at 3:34 PM, Steven D'Aprano <st...@pearwood.info> wrote:
>> Can you give an example of a Python expression, involving PEP 572
>> binding-expressions, that is not a tree but a more general DAG or that
>> contains cycles?
>
> A DAG is a directed *acyclic* graph, so it still can't contain cycles.
> But I have no idea what kind of expression isn't a tree as a
> consequence of having an assignment in it.

At a parsing level, the expression remains a tree, it's just that one
of the nodes is a name lookup. At a logical level though, binding
expressions do indeed mean that expressions involving binding
expressions are at least arguably better modelled with a DAG rather
than as a tree the way they are now:

# The arithmetic expression is a tree including two nodes that
look up "c". The DAG is only needed at a statement level.
c = expr()
a*c + b*c

# Whereas this backref to "c" pulls the DAG down to the expression level
a*(c := expr()) + b*c

Historically, that kind of order-of-evaluation dependence in Python
has only been a problem for functions with side effects, so the folks
asking that this be seen as a major complexity increase for expression
level semantics have an entirely valid point. The PEP aims to address
that point by saying "Don't use binding expressions when the order of
evaluation would be ambiguous", but that's as a response to a valid
concern, not a dismissal of it.

Cheers,
Nick.

--
Nick Coghlan | ncog...@gmail.com | Brisbane, Australia

Łukasz Langa

unread,
Apr 26, 2018, 11:50:07 AM4/26/18
to Steven D'Aprano, pytho...@python.org

> On Apr 25, 2018, at 11:10 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> Criticising binding-
> expressions for that reason, especially implying that we must always use
> parens, is simply FUD.

The PEP has more examples with parentheses than without.

>> As soon as we have to wrap a part of an expression in parentheses,
>> parsing the entire thing becomes more complex.
>
> Unless it becomes less complex to read and understand.

You're ignoring the context of the discussion. The new parentheses are there because there's a new assignment there. That's more complex.


>> Often enough it will
>> cause the expression to exceed whatever line length limit the codebase
>> pledged not to exceed, causing one line to become three.
>
> Just how often are your lines within two characters of the maximum
> column so that adding a pair of brackets () will "often enough" put it
> over the limit? Seems pretty unlikely to me.

Again, you're ignoring the context of the discussion. Assuming := will have spaces around it in PEP 8, this is adding *at least* 7 characters if the name of your choice is a single character.


> This sounds really like
> picking at straws.

The entire point of the PEP is to make things "nicer". It doesn't fundamentally enable programmers to do anything they couldn't do before.

If you think demonstrating cases where the end result won't be an improvement is picking at straws, then maybe the improvement of PEP 572 is as well.

-- Ł

Lukasz Langa

unread,
Apr 26, 2018, 2:17:59 PM4/26/18
to Tim Peters, Raymond Hettinger, Python-Dev@Python. Org
[Uncle T]
> So, to match your sarcasm, here's mine: try using a feature for what
> it's good at instead of for what it's bad at ;-)

Yes, this is the fundamental wisdom. Judging which is which is left as an
exercise to the programmer.

With this, I'm leaving the discussion. With Guido and you on board for PEP
572, I feel that Chris' streak is indeed about to break.

Some parting hair-splitting follows.


> [Uncle T]
>>> One language feature conspicuous by absence in newbie
>>> confusions was, consistently, assignment expressions. Read any book
>>> or tutorial for such a language, and you'll find very little space
>>> devoted to them too.
>
> [Łukasz Langa <luk...@langa.pl>]
>> Well, you have an entire code style built around this feature called Yoda
>> conditions. You teach people on Day 1 to never ever confuse == with =. Some
>> compilers even warn about this because so many people did it wrong.
>
[Uncle T]
> Sorry, I couldn't follow that.

You implied that newbies don't have to even know about assignments in
expressions. I wanted to demonstrate that this isn't really the case because
mistaking `=` for `==` is a relatively common occurence for newbies. If you
want to argue that it isn't, I'd like to point out that the WordPress code
style *requires* Yoda conditions because it was enough of a hindrance. ESLint
(a JavaScript linter) also has a warning about assignment in a conditional.


[Uncle T]]
> In languages like C that use easily
> confused operator symbols, sure, people are forever typing "=" when
> they mean "==". That's nothing to do with whether they _understand_
> what the different operators do, though.

What you're saying is true. But for it to be true, newbies *have to* learn the
distinction, and the fact that yes, sometimes the programmer indeed meant to
put a single `=` sign in the conditional. That's why we'll end up with the
Pascal assignment operator. And that *is* a thing that you will have to
explain to newbies when they encounter it for the first time. Sadly, googling
for a colon followed by an equal sign isn't trivial if you don't know what
you're looking for.


[Łukasz]


>> Well, you can also use it as a statement. But don't!
>

[Uncle T]]
> Why not? _Every_ expression in Python can be used as a statement.
> Nothing forbids it, and that's even (very!) useful at an interactive
> prompt.

Because it suggests different intent, because it's limited, because it's slower
at runtime, and because PEP 572 says so itself.


> At this point I think you must have a lower opinion of Python
> programmers than I have ;-) If adding even a dozen characters to a
> line makes it exceed a reasonable line-length guide, the code was
> almost certainly too confusingly dense to begin with.

Around 5% of if and elif statements in the standard library don't fit a single
line *as is*. Sure, that's a low percentage but that's over 1,000 statements.
If you're putting an `if` statement in a method, you are already starting out
with 71 characters left on the line. Four of those are going to be taken by
"if", a space, and the colon. Adding a parenthesized assignment expression
takes at least 10% of that available space.

The silver lining for me is that this makes the environment riper for
auto-formatting.

Terry Reedy

unread,
Apr 26, 2018, 2:56:18 PM4/26/18
to pytho...@python.org

That is exactly what I wrote in the continuation that Steven snipped.

--
Terry Jan Reedy

Zero Piraeus

unread,
Apr 26, 2018, 3:04:38 PM4/26/18
to Guido van Rossum, Python-Dev@Python. Org
:

On 25 April 2018 at 21:28, Guido van Rossum <gu...@python.org> wrote:
> A very emotional appeal, you don't seem to grasp the usability improvements
> this will give. I hear you but at this point appeals to Python's "Zen" don't
> help you.

I have to admit, in half-following this discussion I've swung between
thinking "there's no way this is actually going to happen" and "what,
this might actually happen?"

Since it now looks like it really *does* have a decent chance, and
maybe another -1 has a small chance of tipping the balance: my
reaction to the proposal is also emotional. Visceral, in fact, to the
extent that I'd aim to read and write less Python if it became
commonplace.

I don't have arguments. I just have an instinctive "ugh, no".

-[]z.

Terry Reedy

unread,
Apr 26, 2018, 3:07:23 PM4/26/18
to pytho...@python.org
On 4/26/2018 6:34 AM, Greg Ewing wrote:
> Antoine Pitrou wrote:
>> Well, how do languages where assignment is an expression returning the
>> assigned value make their REPLs work?  I'm sure they don't inflict that
>> on their users, so it's certainly a solvable problem.
>
> I can't think of any such language that has a REPL
> offhand, but here's a possible solution:
>
>
>    x := expr # doesn't print anything

Ugh! The only reason I would bother typing the : in a top level
assignment would be to get the print without having to retype the name,
as in

>>> x = expr
>>> x

I consider echoing top-level interactive assignments to be a feature of
the proposal.

--
Terry Jan Reedy

Tim Peters

unread,
Apr 26, 2018, 4:35:31 PM4/26/18
to Lukasz Langa, Raymond Hettinger, Python-Dev@Python. Org
[Tim]

>> So, to match your sarcasm, here's mine: try using a feature for what
>> it's good at instead of for what it's bad at ;-)

[Lukasz Langa <luk...@langa.pl>]


> Yes, this is the fundamental wisdom. Judging which is which is left as an
> exercise to the programmer.
>
> With this, I'm leaving the discussion. With Guido and you on board for PEP
> 572, I feel that Chris' streak is indeed about to break.

I still expect it could go either way, but do wish people didn't
believe it will be a major loss if "the other side wins". I'll be
fine regardless - and so will everyone else. Guido rarely makes
language design mistakes. In this case he's seeing serious opposition
from several core developers, and you shouldn't believe either that he
just dismisses that.


[Łukasz Langa]


>>> Well, you have an entire code style built around this feature called Yoda
>>> conditions. You teach people on Day 1 to never ever confuse == with =. Some
>>> compilers even warn about this because so many people did it wrong.

>> Sorry, I couldn't follow that.

Part of the problem here is that I had never seen "Yoda conditions"
before, and had no idea what it meant. Some later Googling suggests
it's "a thing" youngsters say at times ;-)


> You implied that newbies don't have to even know about assignments in
> expressions. I wanted to demonstrate that this isn't really the case because
> mistaking `=` for `==` is a relatively common occurence for newbies. If you
> want to argue that it isn't, I'd like to point out that the WordPress code
> style *requires* Yoda conditions because it was enough of a hindrance. ESLint
> (a JavaScript linter) also has a warning about assignment in a conditional.

What does that have to do with Python? If they try to use "=" in an
expression now, they get a SyntaxError. The PEP doesn't change
anything about that. Indeed, that's why it uses ":=" instead. I have
experience in other languages with embedded assignments that also use
":=", and it's _never_ the case that people type ":=" when they intend
"equality test" in those. The horrid "I typed = when I meant =="
mistakes are unique to languages that mindlessly copied C. The
mistakes aren't primarily due to embedded assignments, they're due to
that even highly experienced programmers sometimes type "=" when
they're _thinking_ "equals". Nobody types ":=" when they're thinking
"equals".


> ...


> What you're saying is true. But for it to be true, newbies *have to* learn the
> distinction, and the fact that yes, sometimes the programmer indeed meant to
> put a single `=` sign in the conditional.

Again, the PEP is about Python: a single "=" in a conditional is, and
will remain, a SyntaxError. So nobody can sanely intend to put a
single "=" in a condition _in Python_ unless they're writing a test
intending to provoke a syntax error.


> That's why we'll end up with the Pascal assignment operator.

":=" is already in the PEP.


> And that *is* a thing that you will have to explain to newbies when they encounter
> it for the first time.

Sure. That doesn't frighten me, though. It's easy to explain what it
does - although it may be hard to explain when it's _desirable_ to use
it.


> Sadly, googling for a colon followed by an equal sign isn't trivial if you don't
> know what you're looking for.

To judge from Stackoverflow volume, the single most misunderstood of
all Python operators - by far - is "is" - try Googling for that ;-)
In far second and third places are "and" and "or", for which searches
are also useless.

Regardless, I'm not concerned about one-time tiny learning curves.
Don't know what ":=" means already? Ask someone. If you know what
"=" means, you're already close to done. Given that you already
understand what "binding a name" means, ":=" may well be the simplest
of all Python's operators (there's no computation _to_ be understood,
and no possibility either of a dunder method changing its meaning
depending on operand type(s)).


>>> Well, you can also use it as a statement. But don't!

>> Why not? _Every_ expression in Python can be used as a statement.


>> Nothing forbids it, and that's even (very!) useful at an interactive
>> prompt.

> Because it suggests different intent, because it's limited, because it's slower
> at runtime, and because PEP 572 says so itself.

I didn't say you're _required_ to use it as a statement. Regardless
of what PEPs say, people will do what they find most useful. I trust
people to figure this out quickly for themselves.


>> At this point I think you must have a lower opinion of Python
>> programmers than I have ;-) If adding even a dozen characters to a
>> line makes it exceed a reasonable line-length guide, the code was
>> almost certainly too confusingly dense to begin with.

> Around 5% of if and elif statements in the standard library don't fit a single
> line *as is*. Sure, that's a low percentage but that's over 1,000 statements.
> If you're putting an `if` statement in a method, you are already starting out
> with 71 characters left on the line. Four of those are going to be taken by
> "if", a space, and the colon. Adding a parenthesized assignment expression
> takes at least 10% of that available space.

Confirming that you do have a lower opinion of them ;-) Are you
picturing people stampeding to introduce ":=" in every place they
possibly could? I may be wrong, but I don't expect that at all. I
expect a vast majority of uses in real life will replace:

name = expression
if name:

by

if name := expression:

and

while True:
name = expression
if name comparison expression2:
break

by

while (name := expression) inverted_comparison expression2:

_provided that_ the latter spelling doesn't make the line
uncomfortably long. In all the code of mine I've seen a good use for
it, there's a whole lot of empty horizontal screen space to spare,
even after recoding. In places where I already had "long lines", I
didn't even check to see whether a binding operation could be used too
- why would I? I don't feel _compelled_ to use it - I'm only looking
to reduce redundancy where it's an obvious win.


> The silver lining for me is that this makes the environment riper for
> auto-formatting.

See? It's win-win for you too no matter how this turns out ;-)

Kirill Balunov

unread,
Apr 26, 2018, 4:52:50 PM4/26/18
to Steve Holden, Python-Dev@Python. Org
I know it is non productive and spamy (I promise, this is the last)  since `as` syntax is dead. In many cases, there is not much difference in perception between `:=` and `as`. But in several situations, like this one and as Ethan pointed up-thread - the expression first syntax makes obvious the intent and linearly readable:

my_func(arg, buffer=[None]*get_size() as buf, size=buf) 

In any case, it is rather an anti-pattern than a good example to follow.

p.s.: as Victor Stinner wrote on twitter that previously, there was a similar PEP in spirit - "PEP 379 -- Adding an Assignment Expression", which was withdrawn. May be it is worth to make a link to it in the current PEP.


With kind regards, 
-gdg

Steven D'Aprano

unread,
Apr 26, 2018, 9:14:48 PM4/26/18
to pytho...@python.org
On Thu, Apr 26, 2018 at 08:00:46PM +0100, Zero Piraeus wrote:

> Since it now looks like it really *does* have a decent chance, and
> maybe another -1 has a small chance of tipping the balance: my
> reaction to the proposal is also emotional. Visceral, in fact, to the
> extent that I'd aim to read and write less Python if it became
> commonplace.

Funnily enough, that's what some people said about decorator syntax,
ternary if, type annotations and list comprehensions.

All of them have become great additions to the language.

I hated the idea of aping C and adding += operators and swore I'd never
use them. That lasted, well, about a month.

Just sayin'.


--
Steve

Tim Peters

unread,
Apr 26, 2018, 11:14:00 PM4/26/18
to Steven D'Aprano, Python Dev
[Zero Piraeus]
>> Since it now looks like it really *does* have a decent chance, and
>> maybe another -1 has a small chance of tipping the balance: my
>> reaction to the proposal is also emotional. Visceral, in fact, to the
>> extent that I'd aim to read and write less Python if it became
>> commonplace.

[Steven D'Aprano <st...@pearwood.info>]
> Funnily enough, that's what some people said about decorator syntax,
> ternary if, type annotations and list comprehensions.
>
> All of them have become great additions to the language.
>
> I hated the idea of aping C and adding += operators and swore I'd never
> use them. That lasted, well, about a month.
>
> Just sayin'.

Well - I've come to respect your opinion, so ... OK, I'll give += a
try. Frankly, I've grown tired of editing it out of all the packages
I download anyway ;-)

Steven D'Aprano

unread,
Apr 27, 2018, 1:45:35 AM4/27/18
to pytho...@python.org
On Thu, Apr 26, 2018 at 08:48:12AM -0700, Łukasz Langa wrote:
>
> > On Apr 25, 2018, at 11:10 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> > Criticising binding-
> > expressions for that reason, especially implying that we must always use
> > parens, is simply FUD.
>
> The PEP has more examples with parentheses than without.

Yes? Parens aren't mandatory, and my point that other operators also
sometimes needs parens still holds.


> >> As soon as we have to wrap a part of an expression in parentheses,
> >> parsing the entire thing becomes more complex.
> >
> > Unless it becomes less complex to read and understand.
>
> You're ignoring the context of the discussion. The new parentheses are
> there because there's a new assignment there. That's more complex.

I'm not ignoring the context of the discussion. I'm comparing binding-
expression with and without parens. That's what I thought you were
doing.

If that wasn't your intended meaning, then I apologise but please
understand why I answered the way I did.

I still stand by my argument: parens are not always needed, and even
when they are not needed, adding them can sometimes make things
*easier* and *less complex* to read.


[...]


> If you think demonstrating cases where the end result won't be an
> improvement is picking at straws, then maybe the improvement of PEP
> 572 is as well.

Any feature can have cases where the end result is worse than not using
the feature. That *alone* isn't a strong argument against a feature.

Do you have much existing code using binding expressions? Of course not.
Will you be adding them to code that already exists? Probably not -- you
can't do so until you are using 3.8 at minimum, and if your code needs
to be backwards compatible, you can't use it until you've dropped
support for 3.7 and older. That might not be for five or ten years.

So it is likely that for most people only new code will use this
feature. It is not reasonable to say that if I have existing code like
this:

spam = expression
if long_condition_that_takes_up_most_of_the_line == spam or spam:
...

that I'm going to immediately change it to a one-liner:

if long_condition_that_takes_up_most_of_the_line == (spam := expression) or spam:
...

and push it over the maximum line width. With or without parentheses.
Why would I do something so silly? Using binding expressions isn't
mandatory and most coders don't intentionally do things that make their
code worse.

And if I wouldn't change existing code and push it over the limit, why
would I write new code that does it? Especially when there are much
better alternatives:

if (long_condition_that_takes_up_most_of_the_line
== (spam:=expression)
or spam):
...


We have a history of adding features that can be abused, but aren't.
People hardly ever abuse list comps with overly long and complex
multiple-loop comprehensions:

[... for a in sequence for b in something for c in another for d in something_else]

I'm sure we've all seen one or two of those. But it doesn't happen
enough to matter. Same with if...elif...else chains. People didn't
immediately run out and replace every single if chain into nested
ternary if expressions, pushing their lines out to beyond the maximum
line width:

expression if condition else (expression if condition else (expression if condition else (expression if condition else expression)))

Real people don't abuse comprehensions or ternary if enough for us to
regret adding them to the language. I'm sure that they won't abuse this
feature either. The Python community simply doesn't have the culture of
abusing syntax in this way and writing overly dense one-liners, and I
don't think it is reasonable to say this feature will tip the balance.

It is reasonable to say that *some* code will be made worse by this,
because there's always *someone* who will abuse syntax. There are those
who insist on writing list comprehensions for their side-effects:

# return and throw away a list of Nones
[print(item) for item in bunch_of_stuff]

but I don't think this happens enough to make us regret adding
comprehensions to the language.


--
Steve

Wes Turner

unread,
Apr 27, 2018, 6:20:29 AM4/27/18
to Steven D'Aprano, pytho...@python.org
So, the style guidelines for this new feature -- and also ternary expressions and comprehension -- would need to mention that:

- debuggers have no idea what to do with all of this on one line
- left-to-right doesn't apply to comprehensions

  results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

- left-to-right doesn't apply to ternary expressions

  if (y := func(x)) if (x := 3) else 0:
  while (y := func(x)) if (x := 3) else 0:

- left-to-right does apply to everything else?

- *these* are discouraged:

  if (x := 3) or (y := func(x)):
  if (3) or (func(3)):
  if ((x := 3) if 1 else (y := func(x))):

IDK, I could just be resistant to change, but this seems like something that will decrease readability -- and slow down code review -- without any real performance gain. So, while this would be useful for golfed-down (!) one-liners with pyline,
I'm -1 on PEP 572.

How do I step through this simple example with a debugger?

    if re.search(pat, text) as match:
        print("Found:", match.group(0))

How do I explain what ':=' is when teaching Python?

AFAIU, semantically: Python = ('equals') indicates a statement. What you are proposing is adding an ':=' ('colon equals') assignment operator which defines a variable which is limited in scope only in list, dict, and generator comprehensions.


> In some languages the symbol used is regarded as an operator (meaning that the assignment has a value) while others define the assignment as a statement (meaning that it cannot be used in an expression).


PEP 572 -- Assignment Expressions
PEP 572 -- Assignment Operator (:=) and Assignment Expressions

Chris Angelico

unread,
Apr 27, 2018, 9:06:20 AM4/27/18
to pytho...@python.org
On Fri, Apr 27, 2018 at 8:18 PM, Wes Turner <wes.t...@gmail.com> wrote:
> IDK, I could just be resistant to change, but this seems like something that
> will decrease readability -- and slow down code review -- without any real
> performance gain. So, while this would be useful for golfed-down (!)
> one-liners with pyline,
> I'm -1 on PEP 572.

PEP 572 has never promised a performance gain, so "without any real
performance gain" is hardly a criticism.

> How do I step through this simple example with a debugger?
>
> if re.search(pat, text) as match:
> print("Found:", match.group(0))

Step the 'if' statement. It will call re.search() and stash the result
in 'match'. Then the cursor would be put either on the 'print' (if the
RE matched) or on the next executable line (if it didn't).

> From https://en.wikipedia.org/wiki/Assignment_(computer_science) :
>
>> In some languages the symbol used is regarded as an operator (meaning that
>> the assignment has a value) while others define the assignment as a
>> statement (meaning that it cannot be used in an expression).
>
>
> PEP 572 -- Assignment Expressions
> PEP 572 -- Assignment Operator (:=) and Assignment Expressions

Huh? I don't get your point.

ChrisA
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Chris Barker

unread,
Apr 27, 2018, 2:20:47 PM4/27/18
to Tim Peters, Raymond Hettinger, Python-Dev@Python. Org
On Thu, Apr 26, 2018 at 1:33 PM, Tim Peters <tim.p...@gmail.com> wrote:

>  And that *is* a thing that you will have to explain to newbies when they encounter
> it for the first time.

Sure.  That doesn't frighten me, though.  It's easy to explain what it
does - although it may be hard to explain when it's _desirable_ to use
it.

I'm with Raymond here -- though I'm not sure "newbies" is quite right -- I've found that newbies fall into two camps: folks to whom programming comes naturally, and those that it doesn't (OK, it's a distribution, but a bimodal one). And folks that are struggling with programming can struggle even with simple assignment (name binding), particularly when you add even function local scope. So having one more way to do assignment WILL make it harder to teach, not because it's that hard, but because it's one more thing to learn.

But the fact is that as Python has evolved (particularly with the jump to py3) it has become less and less of a "scripting" language, and more of a "systems" language. And also harder to learn. Anyone remember CP4E? Python is not as good choice as a "newbie" language as it once was.

Adding := will move it a little bit more along the complexity path -- not much, and that's where Python has gone anyway, so as Tim said, no one's going to suffer either way this decision goes.

Hmm -- I wonder if a "pythonscript" will get forked off one day......

To judge from Stackoverflow volume, the single most misunderstood of
all Python operators - by far - is "is" -

You now, I think instructors like me are partly responsible. "is" is rarely useful outside of comparing to singletons. Yet I use it early in instruction to do checks on name binding and show things with mutablilty, etc.... which has the unfortunate side effect of making it seem like a more common operator than it is.

I've even had students write code like:

if x is 3:

and thanks to interning, it appears to work!

-CHB


--

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

Chris....@noaa.gov

Tim Peters

unread,
Apr 27, 2018, 3:21:45 PM4/27/18
to Chris Barker, Raymond Hettinger, Python-Dev@Python. Org
[Lukasz]
>> > And that *is* a thing that you will have to explain to newbies when
>> > they encounter it for the first time.

[Tim]
>> Sure. That doesn't frighten me, though. It's easy to explain what it
>> does - although it may be hard to explain when it's _desirable_ to use
>> it.

[Chris Barker <chris....@noaa.gov>]
> I'm with Raymond here -- though I'm not sure "newbies" is quite right --
> I've found that newbies fall into two camps: folks to whom programming comes
> naturally, and those that it doesn't (OK, it's a distribution, but a bimodal
> one). And folks that are struggling with programming can struggle even with
> simple assignment (name binding), particularly when you add even function
> local scope.

Sure. What I wrote was shorthand for what's already been covered at
length many times: what a binding expression does is "easy to
explain" GIVEN THAT someone ALREADY UNDERSTANDS how binding a name
works. The latter in fact seems difficult for a significant number of
people to learn, but it's utterly unavoidable that they learn it if
they're ever to write non-trivial Python programs. That's been true
since Python's first release.

Binding expressions would be introduced much later in any sane course.
At THAT point, for students who haven't already dropped out, the
semantics are darned-near trivial to explain: it binds the name to
the object the expression evaluates to (all of which they _already_
understand by this point), and the value of the binding expression is
that object (the only new bit).

Unlike as for most other operators, you don't even have to weasel-word
it to account for that a magical dunder method may change what ":="
does. As for the "is" operator, the meaning is baked into the
language and can't be altered in the slightest.


> So having one more way to do assignment WILL make it harder to
> teach, not because it's that hard, but because it's one more thing to learn.

On a scale of 1 to a million, try to quantify how much harder ;-) As
above, I can't see it getting beyond a single digit, GIVEN THAT a
student has already masteredf the far more complex assignment
_statement_ (binding expressions are limited to the single simplest
case of the many things an assignment statement can do). "And it
returns the object" is a yawn. But, as I already granted, it may be
truly hard to explain when it's a desirable thing to use. That takes
experience and "good judgment", which - according to me - can be
learned but can't really be taught.


> But the fact is that as Python has evolved (particularly with the jump to
> py3) it has become less and less of a "scripting" language, and more of a
> "systems" language. And also harder to learn. Anyone remember CP4E? Python
> is not as good choice as a "newbie" language as it once was.

I agree - although I expect sticking to a subset of Python could make
life easier for beginners. For example, would anyone in their right
mind even mention async gimmicks when teaching beginners?

Against that, though, one of the most unintentionally funny tech
things I ever read was Bjarne Stroustrup writing about why C++ is an
excellent choice for beginners. But he does have a point: if you
throw away the bulk of everything C++ added, there's an easily usable
little language exceedingly well hidden under it all ;-)


> Adding := will move it a little bit more along the complexity path -- not
> much, and that's where Python has gone anyway, so as Tim said, no one's
> going to suffer either way this decision goes.

Yet there will be much wailing and gnashing of teeth anyway ;-)

...
>> To judge from Stackoverflow volume, the single most misunderstood of
>> all Python operators - by far - is "is" -

> You now, I think instructors like me are partly responsible. "is" is rarely
> useful outside of comparing to singletons. Yet I use it early in instruction
> to do checks on name binding and show things with mutablilty, etc.... which
> has the unfortunate side effect of making it seem like a more common
> operator than it is.
>
> I've even had students write code like:
>
> if x is 3:
>
> and thanks to interning, it appears to work!

Yup, that's the real problem with "is": its semantics are dead
simple, but "but under exactly what conditions are `x` and `y` bound
to the same object?" is intractable. It seems to take a long time to
get across the point, that the question itself is misguided. A full
answer requires delving into transient implementation details, which
is counterproductive because they _are_ accidents of the
implementation du jour. What questioners need to be nudged into
asking instead is for examples of when using "is" is thoroughly sane.

Wes Turner

unread,
Apr 27, 2018, 4:08:08 PM4/27/18
to Chris Angelico, pytho...@python.org


On Friday, April 27, 2018, Chris Angelico <ros...@gmail.com> wrote:
On Fri, Apr 27, 2018 at 8:18 PM, Wes Turner <wes.t...@gmail.com> wrote:
> IDK, I could just be resistant to change, but this seems like something that
> will decrease readability -- and slow down code review -- without any real
> performance gain. So, while this would be useful for golfed-down (!)
> one-liners with pyline,
> I'm -1 on PEP 572.

PEP 572 has never promised a performance gain, so "without any real
performance gain" is hardly a criticism.

> How do I step through this simple example with a debugger?
>
>     if re.search(pat, text) as match:
>         print("Found:", match.group(0))

Step the 'if' statement. It will call re.search() and stash the result
in 'match'. Then the cursor would be put either on the 'print' (if the
RE matched) or on the next executable line (if it didn't).

Right. Pdb doesn't step through the AST branches of a line, so ternary expressions and list comprehensions and defining variables at the end of the line are 'debugged' after they're done.

Similarly, code coverage is line-based; so those expressions may appear to be covered but are not.
 

> From https://en.wikipedia.org/wiki/Assignment_(computer_science) :
>
>> In some languages the symbol used is regarded as an operator (meaning that
>> the assignment has a value) while others define the assignment as a
>> statement (meaning that it cannot be used in an expression).
>
>
> PEP 572 -- Assignment Expressions
> PEP 572 -- Assignment Operator (:=) and Assignment Expressions

Huh? I don't get your point.

Q: What is ':='? (How do I searchengine for it?)
A: That's the assignment operator which only works in Python 3.8+.

Q: When are variables defined -- or mutable names bound -- at the end of the expression accessible to the left of where they're defined?
Q: What about tuple unpacking? Is there an ECMAscript-like destructuring PEP yet?
A: Ternary expressions; list, dict, generator comprehensions; 
(@DOCS PLEASE HELP EXPLAIN THIS)

Q: do these examples of the assignment expression operator all work?

"""
- debuggers have no idea what to do with all of this on one line
- left-to-right doesn't apply to comprehensions

  results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

- left-to-right doesn't apply to ternary expressions

  if (y := func(x)) if (x := 3) else 0:
  while (y := func(x)) if (x := 3) else 0:

- left-to-right does apply to everything else?

- *these* are discouraged:

  if (x := 3) or (y := func(x)):
  if (3) or (func(3)):

  if ((x := 3) if 1 else (y := func(x))):
"""

ChrisA
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/wes.turner%40gmail.com

Chris Angelico

unread,
Apr 27, 2018, 4:12:06 PM4/27/18
to pytho...@python.org
On Sat, Apr 28, 2018 at 6:06 AM, Wes Turner <wes.t...@gmail.com> wrote:
>
>
> On Friday, April 27, 2018, Chris Angelico <ros...@gmail.com> wrote:
>>
>> On Fri, Apr 27, 2018 at 8:18 PM, Wes Turner <wes.t...@gmail.com> wrote:
>> > IDK, I could just be resistant to change, but this seems like something
>> > that
>> > will decrease readability -- and slow down code review -- without any
>> > real
>> > performance gain. So, while this would be useful for golfed-down (!)
>> > one-liners with pyline,
>> > I'm -1 on PEP 572.
>>
>> PEP 572 has never promised a performance gain, so "without any real
>> performance gain" is hardly a criticism.
>>
>> > How do I step through this simple example with a debugger?
>> >
>> > if re.search(pat, text) as match:
>> > print("Found:", match.group(0))
>>
>> Step the 'if' statement. It will call re.search() and stash the result
>> in 'match'. Then the cursor would be put either on the 'print' (if the
>> RE matched) or on the next executable line (if it didn't).
>
>
> Right. Pdb doesn't step through the AST branches of a line, so ternary
> expressions and list comprehensions and defining variables at the end of the
> line are 'debugged' after they're done.
>
> Similarly, code coverage is line-based; so those expressions may appear to
> be covered but are not.

I'm not sure I follow. In what situation would some code appear to be
covered when it isn't, due to an assignment expression?

ChrisA
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Wes Turner

unread,
Apr 27, 2018, 4:26:22 PM4/27/18
to Chris Angelico, pytho...@python.org
When an assignment expression is in the else clause of a ternary expression, but the else clause does not execute because the condition is true, the assignment expression does not execute and so isn't covered.

if ((1) or (x := 3)):
if ((y := func(x)) if x else (x := 3))

Is this a new opportunity for misunderstanding?

Assignment expressions, though they are noticeable :=, may not actually define the variable in cases where that part of the line doesn't run but reads as covered.


 

ChrisA
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/wes.turner%40gmail.com

Chris Angelico

unread,
Apr 27, 2018, 4:31:44 PM4/27/18
to pytho...@python.org
Okay. How is this different from anything else involving if/else
expressions? If your code coverage checker is unable to handle
if/else, it's unable to handle it whether there's an assignment in
there or not.

I don't understand why people bring up all these arguments that have
absolutely nothing to do with the proposal at hand. None of this has
in any way changed.

ChrisA
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Tim Peters

unread,
Apr 27, 2018, 5:13:39 PM4/27/18
to Chris Angelico, pytho...@python.org
[Chris Angelico <ros...@gmail.com>]
> ...
> I don't understand why people bring up all these arguments that have
> absolutely nothing to do with the proposal at hand. None of this has
> in any way changed.

That's easy: any time there's a long thread to which Guido has
contributed at least twice, it will be seen as a Golden Opportunity to
re-litigate every decision that's ever been made ;-)

Some amount of that seems healthy to me (people are thinking about
"language design" from a larger view than the proposal du jour). In
this specific case, line-oriented coverage tools have missed
accounting for all possible code paths since day #1; e.g.,

x = f() or g()

You don't need to reply to messages so obviously irrelevant to the PEP
unless you want to. It's not like Guido will read them and go "oh! a
binding expression in a ternary conditional is a fundamentally new
potential problem for a line-oriented coverage tool! that's fatal"
;-)

Wes Turner

unread,
Apr 27, 2018, 5:36:33 PM4/27/18
to Tim Peters, pytho...@python.org


On Friday, April 27, 2018, Tim Peters <tim.p...@gmail.com> wrote:
[Chris Angelico <ros...@gmail.com>]
> ...
> I don't understand why people bring up all these arguments that have
> absolutely nothing to do with the proposal at hand. None of this has
> in any way changed.

That's easy:  any time there's a long thread to which Guido has
contributed at least twice, it will be seen as a Golden Opportunity to
re-litigate every decision that's ever been made ;-)

Some amount of that seems healthy to me (people are thinking about
"language design" from a larger view than the proposal du jour).  In
this specific case, line-oriented coverage tools have missed
accounting for all possible code paths since day #1; e.g.,

    x = f() or g()

You don't need to reply to messages so obviously irrelevant to the PEP
unless you want to.  It's not like Guido will read them and go "oh!  a
binding expression in a ternary conditional is a fundamentally new
potential problem for a line-oriented coverage tool!  that's fatal"
;-)

I have shared with you the overlapping concerns about this feature proposal that I believe should be explained with DO and DON'T in the docs and/or the PEP and/or the style guide(s) for various organizations in the Pyrhon community.

This feature does require additions to the style-guide(s); which is why so many have expressed concern about such a simple thing.

If you want to write debuggable and coverage-testable code, do not use the assignment expression operator in ternary expressions or boolean-chained expressions.

The assignment expression operator is the only way to define variables with only comprehension scope.

Do not do this:

x = 2
if (x == 3) or (x := 3):
   print(x)

What do we call that mistake?
 
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/wes.turner%40gmail.com

Chris Angelico

unread,
Apr 27, 2018, 5:44:43 PM4/27/18
to pytho...@python.org
On Sat, Apr 28, 2018 at 7:11 AM, Tim Peters <tim.p...@gmail.com> wrote:
> [Chris Angelico <ros...@gmail.com>]
>> ...
>> I don't understand why people bring up all these arguments that have
>> absolutely nothing to do with the proposal at hand. None of this has
>> in any way changed.
>
> That's easy: any time there's a long thread to which Guido has
> contributed at least twice, it will be seen as a Golden Opportunity to
> re-litigate every decision that's ever been made ;-)

Well, now, that explains a lot! :-)

> Some amount of that seems healthy to me (people are thinking about
> "language design" from a larger view than the proposal du jour). In
> this specific case, line-oriented coverage tools have missed
> accounting for all possible code paths since day #1; e.g.,
>
> x = f() or g()
>
> You don't need to reply to messages so obviously irrelevant to the PEP
> unless you want to. It's not like Guido will read them and go "oh! a
> binding expression in a ternary conditional is a fundamentally new
> potential problem for a line-oriented coverage tool! that's fatal"
> ;-)

True, but sometimes it takes two or three emails before I actually
understand the objection enough to know that it's actually irrelevant
:| I'm going to start ignoring any message that I don't understand, in
the hopes that it doesn't actually mean anything. :|

ChrisA

Tres Seaver

unread,
Apr 27, 2018, 5:45:10 PM4/27/18
to pytho...@python.org
On 04/27/2018 05:11 PM, Tim Peters wrote:

> In this specific case, line-oriented coverage tools have missed
> accounting for all possible code paths since day #1; e.g.,
>
> x = f() or g()
>
> You don't need to reply to messages so obviously irrelevant to the PEP
> unless you want to. It's not like Guido will read them and go "oh! a
> binding expression in a ternary conditional is a fundamentally new
> potential problem for a line-oriented coverage tool! that's fatal" ;-)
FWIW, Ned Batchelder's 'coverage.py' does a good job with branch coverage.
I haven't seen anything in this discussion which indicates that binding
expressions will change that at all.


Tres.
--
===================================================================
Tres Seaver +1 540-429-0999 tse...@palladion.com
Palladion Software "Excellence by Design" http://palladion.com

Tim Peters

unread,
Apr 27, 2018, 5:56:38 PM4/27/18
to Wes Turner, pytho...@python.org
Wes, sorry, but I really don't follow what you're saying. For example,

[Wes Turner <wes.t...@gmail.com>]
> Do not do this:
>
> x = 2
> if (x == 3) or (x := 3):
> print(x)
>
> What do we call that mistake?

It displays 3 - while it appears to be silly code, there's nothing
about it that's undefined. So I fail to see how showing that example
anywhere would do anyone any good.

You can do the same kind of thing today via, e.g.,

class Bindable:
def __init__(self, value):
self.bind(value)

def bind(self, value):
self.value = value
return value

def __bool__(self):
return bool(self.value)

def __eq__(self, other):
return self.value == other

def __str__(self):
return str(self.value)

Then:

>>> x = Bindable(2)
>>> if x == 3 or x.bind(3):
... print(x)
3

And I wouldn't put that example anywhere in any docs either ;-)
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Tim Peters

unread,
Apr 27, 2018, 6:23:53 PM4/27/18
to Tres Seaver, Python Dev
[Tres Seaver <tse...@palladion.com>]
> FWIW, Ned Batchelder's 'coverage.py' does a good job with branch coverage.
> I haven't seen anything in this discussion which indicates that binding
> expressions will change that at all.

I don't think you missed anything relevant either ;-) Binding
operators are exactly as irrelevant to control-flow analyzers as,
e.g., introducing a floor division operator (//) was.

Data-flow analyzers (if there are any for Python) are a different
story, since they need to be aware of all (re)binding operations -
although at the byte code level, all such sites remain equally
apparent (no new flavor of "store" operation is added by this PEP).

Wes Turner

unread,
Apr 27, 2018, 6:27:59 PM4/27/18
to Tim Peters, pytho...@python.org
It's certainly a contrived example. Actual code with such a mistake is generally far more subtle.

The mistake is that it's assigning a value within a clause of a conditional that won't be evaluated.

Oh well. I'll suffer the then worsened zig-zaggy eye movements in code reviews caused by defining values at the end of expressions that reference them which fit on a single line.

There are a number of bad examples for style guides in these threads.

:=

I wasn't aware of this switch, thanks!
coverage run --branch code.py



On Friday, April 27, 2018, Tim Peters <tim.p...@gmail.com> wrote:

Glenn Linderman

unread,
Apr 27, 2018, 8:00:10 PM4/27/18
to pytho...@python.org
On 4/27/2018 2:11 PM, Tim Peters wrote:
That's easy:  any time there's a long thread to which Guido has
contributed at least twice, it will be seen as a Golden Opportunity to
re-litigate every decision that's ever been made ;-)
You're getting pretty good at that QOTD thing, Tim :)

Armin Rigo

unread,
Apr 27, 2018, 11:10:38 PM4/27/18
to Raymond Hettinger, Python-Dev@Python. Org
Hi,

On 26 April 2018 at 07:50, Raymond Hettinger
<raymond....@gmail.com> wrote:
>> [Raymond Hettinger <raymond....@gmail.com>]
>>> After re-reading all the proposed code samples, I believe that
>>> adopting the PEP will make the language harder to teach to people
>>> who are not already software engineers.
>
> (...)
>
> Python is special, in part, because it is not one of those languages.
> It has virtues that make it suitable even for elementary school children.
> We can show well-written Python code to non-computer folks and walk
> them through what it does without their brains melting (something I can't
> do with many of the other languages I've used). There is a virtue
> in encouraging simple statements that read like English sentences
> organized into English-like paragraphs, presenting itself like
> "executable pseudocode".

I must admit that when I heard about this PEP I thought "this April
1st joke was already done long ago". I'm sorry to discover that, this
time, it is not actually one. Thank you, Raymond, for an unlikely
attempt at reminding people what made Python so special---in your
opinion, and mine.


A bientôt,

Armin.

Cesare Di Mauro

unread,
Apr 28, 2018, 2:34:08 AM4/28/18
to Armin Rigo, Raymond Hettinger, Python-Dev@Python. Org
Hi,

2018-04-28 5:08 GMT+02:00 Armin Rigo <armin...@gmail.com>:
Hi,

On 26 April 2018 at 07:50, Raymond Hettinger
<raymond....@gmail.com> wrote:
>> [Raymond Hettinger <raymond....@gmail.com>]
>>> After re-reading all the proposed code samples, I believe that
>>> adopting the PEP will make the language harder to teach to people
>>> who are not already software engineers.
>
> (...)
>
> Python is special, in part, because it is not one of those languages.
> It has virtues that make it suitable even for elementary school children.
> We can show well-written Python code to non-computer folks and walk
> them through what it does without their brains melting (something I can't
> do with many of the other languages I've used).  There is a virtue
> in encouraging simple statements that read like English sentences
> organized into English-like paragraphs, presenting itself like
> "executable pseudocode".

I must admit that when I heard about this PEP I thought "this April
1st joke was already done long ago".  I'm sorry to discover that, this
time, it is not actually one.  Thank you, Raymond, for an unlikely
attempt at reminding people what made Python so special---in your
opinion, and mine.


A bientôt,

Armin.

Same feeling here. What I really appreciate of Python from long time is its readability: the fact that usually I read the code as English-like sentences.

It was nice to see the usage of the "as" keyword in the try/except construct as well as in the with one, instead of introducing another bunch of symbols which will make it more difficult to decode the meaning of the writing.
Same for the "if/else" ternary operator, which I read like "[give] x if cond else y", instead of the cryptic "?:" of C-like languages. It was a nice and wise design decision.

For similar reasons, I did/don't like the @ for matrix multiplication because it doesn't give me any immediately, useful information which makes it easier to decode the meaning. A "mul" binary operator would have worked better, for example.

I hope that Python core developers refuse the temptation to introduce new operators using symbols for new features: it's a short way to keep backward-compatibility, for sure, but if the price to pay is the readability, then I don't think that it's worth to do it.

Regarding the assignment operator, I also find it a (bad, since it's not so much readable inside expressions) duplicate of the assignment statement. To be more precise, why should we keep the latter once with the former we can do the same things (and more)? Then drop the assignment statement and just leave the operator!

BTW, as a pythonist I've also felt the need to have some way to "bind" values to variables in some context, but it's pretty much related to comprehensions, for obvious reasons I think.
I would have appreciated an "as" keyword, only inside such constructs, but I don't see any value extending it for any generic context, since we already have the assignment statement which works quite well and doesn't introduce nasty side-effects "ala C-like languages".
So, IMO it's better to stay as we are instead of introducing another kludge to the language, if we cannot maintain a good readability.

Cheers,

Cesare

Steven D'Aprano

unread,
Apr 28, 2018, 4:35:34 AM4/28/18
to pytho...@python.org
On Fri, Apr 27, 2018 at 04:24:35PM -0400, Wes Turner wrote:

> if ((1) or (x := 3)):
> if ((y := func(x)) if x else (x := 3))

Wes, there is *absolutely nothing new* here. This sort of error is
already possible in Python.

Do you see a lot of code like:

if (1 or sequence.append(3) or sequence[-1]):

in real life? If you do, then I'm really, really sorry that you are
forced to deal with such rubbish code, but honestly, the vast bulk of
Python programmers do not write like that, and they won't write this
either:

if (1 or (x := 3)):


[...]
> Assignment expressions, though they are noticeable :=, may not actually
> define the variable in cases where that part of the line doesn't run but
> reads as covered.

The same applies to any operation at all.

/sarcasm
I guess adding print() to the language was a mistake, because we
can write rubbish code like this:

if 1 or print(x):

and then be confused by the fact that x doesn't get printed.
/end sarcasm

In another post, you insisted that we need to warn in the PEP and the
docs not to do this sort of thing. Should we also go through and add
these warnings to list.append, dict.update, set.add, etc?

I trust that the answer to that is obviously no.

And neither do we have to assume that people who use binding-expressions
will lose their minds and start writing the sort of awful code that
they don't write with anything else.



--
Steve
_______________________________________________
Python-Dev mailing list
Pytho...@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/dev-python%2Bgarchive-30976%40googlegroups.com

Steve Holden

unread,
Apr 28, 2018, 5:58:56 AM4/28/18
to Tim Peters, Raymond Hettinger, Chris Barker, Python-Dev@Python. Org
On Fri, Apr 27, 2018 at 8:19 PM, Tim Peters <tim.p...@gmail.com> wrote:
[Lukasz]
>> >  And that *is* a thing that you will have to explain to newbies when
>> > they encounter it for the first time.

​Which they will presumably do either in class or by reading code. No sensible instructor or course author is going to bring name-binding expressions up until standard assignment has been thoroughly assimilated. In my own teaching experience I observed that those used to static languages took a little time to adapt to the indirection of Python's names, but not long.
 
​[...]

Sure.  What I wrote was shorthand for what's already been covered at
length many times:  what a binding expression does is "easy to
explain" GIVEN THAT someone ALREADY UNDERSTANDS how binding a name
works.  The latter in fact seems difficult for a significant number of
people to learn, but it's utterly unavoidable that they learn it if
they're ever to write non-trivial Python programs.  That's been true
since Python's first release.

​I was half-expecting someone to pop up and suggest only functional programming as a means to avoid having to teach assignment ...
 
Binding expressions would be introduced much later in any sane course.
At THAT point, for students who haven't already dropped out, the
semantics are darned-near trivial to explain:  it binds the name to
the object the expression evaluates to (all of which they _already_
understand by this point), and the value of the binding expression is
that object (the only new bit).

Unlike as for most other operators, you don't even have to weasel-word
it to account for that a magical dunder method may change what ":="
does.  As for the "is" operator, the meaning is baked into the
language and can't be altered in the slightest.


> So having one more way to do assignment WILL make it harder to
> teach, not because it's that hard, but because it's one more thing to learn.

​But surely that depends on HOW MUCH of the language you aim to teach. ​Over the years Python has become a much more complex language, but it has a fairly easily-identified subset that can act as a basis for building useful programs. Some instructors avoided teaching comprehensions, but a sensible course would try to ensure that students could understand the code they were most likely to encounter "in the wild."

[
​...]
> You now, I think instructors like me are partly responsible. "is" is rarely
> useful outside of comparing to singletons. Yet I use it early in instruction
> to do checks on name binding and show things with mutablilty, etc.... which
> has the unfortunate side effect of making it seem like a more common
> operator than it is.
>
​I'd expand that to say that identity comparison is most useful for types whose instances are all unique. For other types there's the unfortunate impedance mismatch between identity and equality (which is user-definable anyway).
 
> I've even had students write code like:
>
> if x is 3:
>
> and thanks to interning, it appears to work!

​No, thanks to interning it DOES work. For interned values.​
 
​But instructors have to touch on implementation artefacts at times, and I used to find it instructive to write the same code with two different integer constants and ask why they gave different results. It certainly helped people to master the semantics of assignment (as did the phrase "Python never copies data on assignment").​

Yup, that's the real problem with "is":  its semantics are dead
simple, but "but under exactly what conditions are `x` and `y` bound
to the same object?" is intractable.  It seems to take a long time to
get across the point, that the question itself is misguided.  A full
answer requires delving into transient implementation details, which
is counterproductive because they _are_ accidents of the
implementation du jour.  What questioners need to be nudged into
asking instead is for examples of when using "is" is thoroughly sane.

​I'd argue that without some knowledge of the potential pitfalls students can't be expected to learn how to make that distinction.

regards
 Steve

Ned Batchelder

unread,
Apr 28, 2018, 7:26:57 AM4/28/18
to pytho...@python.org
On 4/27/18 5:28 PM, Tres Seaver wrote:
> On 04/27/2018 05:11 PM, Tim Peters wrote:
>
>> In this specific case, line-oriented coverage tools have missed
>> accounting for all possible code paths since day #1; e.g.,
>>
>> x = f() or g()
>>
>> You don't need to reply to messages so obviously irrelevant to the PEP
>> unless you want to. It's not like Guido will read them and go "oh! a
>> binding expression in a ternary conditional is a fundamentally new
>> potential problem for a line-oriented coverage tool! that's fatal" ;-)
> FWIW, Ned Batchelder's 'coverage.py' does a good job with branch coverage.
> I haven't seen anything in this discussion which indicates that binding
> expressions will change that at all.
>
>

Coverage.py can measure branch coverage, but it is still line-oriented. 
There's no support for conditionals and branches within a line (though
I've done some wicked hacks to experiment with it:
https://nedbatchelder.com/blog/200804/wicked_hack_python_bytecode_tracing.html).

It's entirely true that binding expressions don't change this situation
at all, EXCEPT that the entire point of binding expressions is to be
able to express in one statement what used to take more than one.  With
binding expressions, actions may be coverage-hidden within one statement
that without them would have been coverage-visible in more than one
statement.

I'm not sure that's an argument against binding expressions, but we
should at least acknowledge that the motivation for them is to provide
the option to write fewer, longer statements.  That option is not always
a good idea, for a variety of reasons.

--Ned.

Chris Angelico

unread,
Apr 28, 2018, 7:39:06 AM4/28/18
to python-dev
On Sat, Apr 28, 2018 at 9:18 PM, Ned Batchelder <n...@nedbatchelder.com> wrote:
> It's entirely true that binding expressions don't change this situation at
> all, EXCEPT that the entire point of binding expressions is to be able to
> express in one statement what used to take more than one. With binding
> expressions, actions may be coverage-hidden within one statement that
> without them would have been coverage-visible in more than one statement.

So far, all the examples in the PEP have the exact same coverage with
and without assignment expressions, with a few exceptions where
coverage is *improved* by them (where the alternative is to duplicate
a function call). By combining multiple lines into one, we also ensure
that all of it is executed exactly once, instead of having conditional
execution.

ChrisA

Rob Cliffe via Python-Dev

unread,
May 5, 2018, 3:52:52 PM5/5/18
to pytho...@python.org
Reading this sub-thread, it struck me that a good way to make PEP 562
more likely to be accepted is to launch an over-the-top attack on it.
Then more moderate people - who were/are not necessarily in favour of
the PEP - feel pressurised into defending it.

Hah!
Watch this space for my vicious, vitriolic, withering attack on PEP 463
(Exception-catching expressions)! :-)

Best wishes
Rob Cliffe
Reply all
Reply to author
Forward
0 new messages