[Python-Dev] assignment expressions: an alternative proposal

907 views
Skip to first unread message

Yury Selivanov

unread,
Apr 24, 2018, 9:42:23 AM4/24/18
to Python-Dev
I propose to use the following syntax for assignment expressions:

( NAME = expr )

I know that it was proposed before and this idea was rejected, because
accidentally using '=' in place of '==' is a pain point in
C/C++/JavaScript.

That said, I believe we can still use this syntax as long as we impose
the following three restrictions on it:

1. Only NAME token is allowed as a single target.

2. Parenthesis are required.

3. Most importantly: it is *not* allowed to mask names in the current
local scope.


Let's see how each restriction affects the syntax in detail:

(1) NAME tokens only:

if (a[1] = value) # SyntaxError
if (a.attr = value) # SyntaxError

(2) Required parens disambiguate the new syntax from keyword-arguments
and prevent using '=' in place of '==':

if a = value # SyntaxError
if expr and a = value # SyntaxError

(3) No masking of existing names in local scope makes using '=' in
place of '==' by mistake even less probable:

flag = get_flag()
...
if (flag = 'win') # SyntaxError

# or

def foo(value):
if (value = 1) # SyntaxError

# or

py> (c = 1) and (c = 2) # SyntaxError

# etc


The following code snippets are perfectly valid though:

py> a = (b = (c = 3))
py> a, b, c
(3, 3, 3)

# and

py> f = lambda x: x * 10
py> [[(y = f(x)), x/y] for x in range(1,5)]
[[10, 0.1], [20, 0.1], [30, 0.1], [40, 0.1]]

# and

def read():
while (command = input("> ")) != "quit":
print('you entered', command)

# and

py> if (match = re.search(r'wor\w+', 'hello world')):
py. print(match)
<re.Match object; span=(6, 11), match='world'>

# and

if (diff = x - x_base) and (g = gcd(diff, n)) > 1:
return g


Enabling '=' for assignment expressions introduces a limited form of
the more general assignment statement. It is designed to be useful in
expressions and is deliberately simple to make it hard for users to
shoot in the foot. The required Python grammar changes are simple and
unambiguous.

Although it is still possible to accidentally mask a global name or a
name from an outer scope, the risk of that is significantly lower than
masking a local name. IDEs and linters can improve the usability
further by highlighting invalid or suspicious assignment expressions.

I believe that this syntax is the best of both worlds: it allows to
write succinct code just like PEP 572, but without introducing a new
':=' operator.

An implementation of this proposal is available here:
https://github.com/1st1/cpython/tree/assign.

If this idea is deemed viable I will write a PEP detailing the
grammar/compiler changes and syntax restrictions.

Thanks,
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

Nick Coghlan

unread,
Apr 24, 2018, 9:48:24 AM4/24/18
to Yury Selivanov, Python-Dev
On 24 April 2018 at 23:38, Yury Selivanov <yseliv...@gmail.com> wrote:
> I propose to use the following syntax for assignment expressions:
>
> ( NAME = expr )
>
> I know that it was proposed before and this idea was rejected, because
> accidentally using '=' in place of '==' is a pain point in
> C/C++/JavaScript.
>
> That said, I believe we can still use this syntax as long as we impose
> the following three restrictions on it:
>
> 1. Only NAME token is allowed as a single target.
>
> 2. Parenthesis are required.
>
> 3. Most importantly: it is *not* allowed to mask names in the current
> local scope.

While I agree this would be unambiguous to a computer, I think for
most humans it would be experienced as a confusing set of arcane and
arbitrary rules about what "=" means in Python.

Cheers,
Nick.

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

Yury Selivanov

unread,
Apr 24, 2018, 9:53:04 AM4/24/18
to Nick Coghlan, Python-Dev
On Tue, Apr 24, 2018 at 9:46 AM, Nick Coghlan <ncog...@gmail.com> wrote:
> On 24 April 2018 at 23:38, Yury Selivanov <yseliv...@gmail.com> wrote:
>> I propose to use the following syntax for assignment expressions:
>>
>> ( NAME = expr )
>>
>> I know that it was proposed before and this idea was rejected, because
>> accidentally using '=' in place of '==' is a pain point in
>> C/C++/JavaScript.
>>
>> That said, I believe we can still use this syntax as long as we impose
>> the following three restrictions on it:
>>
>> 1. Only NAME token is allowed as a single target.
>>
>> 2. Parenthesis are required.
>>
>> 3. Most importantly: it is *not* allowed to mask names in the current
>> local scope.
>
> While I agree this would be unambiguous to a computer, I think for
> most humans it would be experienced as a confusing set of arcane and
> arbitrary rules about what "=" means in Python.

I respectfully disagree. There are no "arcane and confusing rules"
about "=", it's rather simple:

"=" is always an assignment.
"==" is always an equality check.

Having two assignment operators feels way more arcane to me.
Especially in Python guided by "there should be one way" Zen.

Yury

Antoine Pitrou

unread,
Apr 24, 2018, 9:57:31 AM4/24/18
to pytho...@python.org
On Tue, 24 Apr 2018 23:46:34 +1000
Nick Coghlan <ncog...@gmail.com> wrote:
> On 24 April 2018 at 23:38, Yury Selivanov <yseliv...@gmail.com> wrote:
> > I propose to use the following syntax for assignment expressions:
> >
> > ( NAME = expr )
> >
> > I know that it was proposed before and this idea was rejected, because
> > accidentally using '=' in place of '==' is a pain point in
> > C/C++/JavaScript.
> >
> > That said, I believe we can still use this syntax as long as we impose
> > the following three restrictions on it:
> >
> > 1. Only NAME token is allowed as a single target.
> >
> > 2. Parenthesis are required.
> >
> > 3. Most importantly: it is *not* allowed to mask names in the current
> > local scope.
>
> While I agree this would be unambiguous to a computer, I think for
> most humans it would be experienced as a confusing set of arcane and
> arbitrary rules about what "=" means in Python.

If the ambition is to find a piece of syntax that reads as "binds",
then we can use a variation on the FLUFL operator: "<->".

Regards

Antoine.

Nick Coghlan

unread,
Apr 24, 2018, 10:09:29 AM4/24/18
to Yury Selivanov, Python-Dev
On 24 April 2018 at 23:50, Yury Selivanov <yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 9:46 AM, Nick Coghlan <ncog...@gmail.com> wrote:
>> On 24 April 2018 at 23:38, Yury Selivanov <yseliv...@gmail.com> wrote:
>>> I propose to use the following syntax for assignment expressions:
>>>
>>> ( NAME = expr )
>>>
>>> I know that it was proposed before and this idea was rejected, because
>>> accidentally using '=' in place of '==' is a pain point in
>>> C/C++/JavaScript.
>>>
>>> That said, I believe we can still use this syntax as long as we impose
>>> the following three restrictions on it:
>>>
>>> 1. Only NAME token is allowed as a single target.
>>>
>>> 2. Parenthesis are required.
>>>
>>> 3. Most importantly: it is *not* allowed to mask names in the current
>>> local scope.
>>
>> While I agree this would be unambiguous to a computer, I think for
>> most humans it would be experienced as a confusing set of arcane and
>> arbitrary rules about what "=" means in Python.
>
> I respectfully disagree. There are no "arcane and confusing rules"
> about "=", it's rather simple:
>
> "=" is always an assignment.
> "==" is always an equality check.

That's not the distinction I meant, I meant the difficulty of
explaining the discrepancies in this list:

a = 1 # Assignment
(a = 1) # Also assignment

a, b = 1, 2 # Tuple assignment
(a, b = 1, 2) # SyntaxError. Why?

a.b = 1 # Attribute assignment
(a.b = 1) # SyntaxError. Why?

a[b] = 1 # Subscript assignment
(a[b] = 1) # SyntaxError. Why?


(a=1), (b=2) # Two assignments
a=1, b=2 # SyntaxError. Why?

f(a=1, b=2) # Function call with keyword args
(a=1, b=2) # SyntaxError. Why?

if (a=1): pass # Assignment
if a=1: pass # SyntaxError. Why?

Whereas if binding expressions use a different symbol, the question is
far less likely to arise, and if it does come up, then the answer is
the same as the one for def statements vs lambda expressions: because
one is a statement, and the other is an expression.

Cheers,
Nick.

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

Yury Selivanov

unread,
Apr 24, 2018, 10:32:14 AM4/24/18
to Nick Coghlan, Python-Dev
On Tue, Apr 24, 2018 at 10:07 AM, Nick Coghlan <ncog...@gmail.com> wrote:

>> "=" is always an assignment.
>> "==" is always an equality check.
>
> That's not the distinction I meant, I meant the difficulty of
> explaining the discrepancies in this list:
>
> a = 1 # Assignment
> (a = 1) # Also assignment
>
> a, b = 1, 2 # Tuple assignment
> (a, b = 1, 2) # SyntaxError. Why?
>
> ...
> Whereas if binding expressions use a different symbol, the question is
> far less likely to arise, and if it does come up, then the answer is
> the same as the one for def statements vs lambda expressions: because
> one is a statement, and the other is an expression.

A lot of other questions arise though. PEP 572 proposes:

a = 1 # assignment
a := 1 # also assignment
(a := 1) # also assignment
(a = 1) # error, why?

It's also difficult to explain which one to use when. The net result
is that code will be littered with both at random places. That will
decrease the readability of Python code at least for some users who
have similar taste to myself.

With '=' in expressions, the code will look uniform. There will be a
simple rule to put parens around assignments in expression and use
simple names. After one or two descriptive SyntaxError users will
learn how this syntax works (like people learn everything in coding).

This all is very subjective.

Yury

Paul Moore

unread,
Apr 24, 2018, 10:51:47 AM4/24/18
to Nick Coghlan, Python-Dev
On 24 April 2018 at 14:46, Nick Coghlan <ncog...@gmail.com> wrote:
> On 24 April 2018 at 23:38, Yury Selivanov <yseliv...@gmail.com> wrote:
>> I propose to use the following syntax for assignment expressions:
>>
>> ( NAME = expr )
>>
>> I know that it was proposed before and this idea was rejected, because
>> accidentally using '=' in place of '==' is a pain point in
>> C/C++/JavaScript.
>>
>> That said, I believe we can still use this syntax as long as we impose
>> the following three restrictions on it:
>>
>> 1. Only NAME token is allowed as a single target.
>>
>> 2. Parenthesis are required.
>>
>> 3. Most importantly: it is *not* allowed to mask names in the current
>> local scope.
>
> While I agree this would be unambiguous to a computer, I think for
> most humans it would be experienced as a confusing set of arcane and
> arbitrary rules about what "=" means in Python.

Also, there's the ambiguity and potential for misreading in the
opposite direction (accidentally *reading* = as == even though it
isn't):

if (diff = x - x_base) and (g = gcd(diff, n)) > 1:
return g

My immediate reading of this is as an equality comparison between diff
and x - x_base (which would send me futilely looking for a definition
of diff) and an equality comparison of g and gcd(diff, n)... wait, why
am I doing (equality comparison) > 1? Oh, hang on... At this point,
any hope of me quickly understanding what this code does is lost.

Paul

Nick Coghlan

unread,
Apr 24, 2018, 10:56:05 AM4/24/18
to Yury Selivanov, Python-Dev
On 25 April 2018 at 00:23, Yury Selivanov <yseliv...@gmail.com> wrote:
> A lot of other questions arise though. PEP 572 proposes:
>
> a = 1 # assignment
> a := 1 # also assignment
> (a := 1) # also assignment
> (a = 1) # error, why?

That's just the typical assignment/expression dichotomy, though, which
is genuinely confusing for learners (since expression-level Python and
statement-level Python allow different constructs), but also not a new
problem.

All the other keywords that have both statement level and expression
level use cases are structured as prefix operators in statement form,
and some kind of infix operator in expression form, whereas this would
be the first case where we offered a construct that used infix syntax
for both its statement form and its expression form.

> It's also difficult to explain which one to use when. The net result
> is that code will be littered with both at random places. That will
> decrease the readability of Python code at least for some users who
> have similar taste to myself.

That's a legitimate concern with PEP 572 (and part of why I'm
somewhere between -1 and -0 on the ":=" spelling, although I'd be +0
on an "is=" spelling that riffs off the "is" comparison operator -
using the "name is= expr" spelling in place of a regular assignment
looks sufficiently odd that I'd expect the temptation to write it in
place of "name = expr" when the latter is permitted would be low)

> With '=' in expressions, the code will look uniform. There will be a
> simple rule to put parens around assignments in expression and use
> simple names. After one or two descriptive SyntaxError users will
> learn how this syntax works (like people learn everything in coding).

Except that they'll also find other discrepancies like:

a = 1
a = 2

being OK, while:

a = 1
(a = 2)

fails with SyntaxError on the second line.

Cheers,
Nick.

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

Chris Angelico

unread,
Apr 24, 2018, 10:58:17 AM4/24/18
to Python-Dev
On Wed, Apr 25, 2018 at 12:23 AM, Yury Selivanov
<yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 10:07 AM, Nick Coghlan <ncog...@gmail.com> wrote:
>
>>> "=" is always an assignment.
>>> "==" is always an equality check.
>>
>> That's not the distinction I meant, I meant the difficulty of
>> explaining the discrepancies in this list:
>>
>> a = 1 # Assignment
>> (a = 1) # Also assignment
>>
>> a, b = 1, 2 # Tuple assignment
>> (a, b = 1, 2) # SyntaxError. Why?
>>
>> ...
>> Whereas if binding expressions use a different symbol, the question is
>> far less likely to arise, and if it does come up, then the answer is
>> the same as the one for def statements vs lambda expressions: because
>> one is a statement, and the other is an expression.
>
> A lot of other questions arise though. PEP 572 proposes:
>
> a = 1 # assignment
> a := 1 # also assignment
> (a := 1) # also assignment
> (a = 1) # error, why?

Your third example is just the same as the second, with parentheses
around it. In most of Python, parentheses (if legal) have no effect
other than grouping; "a + b * c" is the same thing as "(a + b) * c",
just done in the other order. The last one is a clear demonstration
that "=" is a statement, not an expression. Are people confused by
this sort of thing:

if x > 1:
print("x is more than 1")
(if x > 1:)
print("SyntaxError")

? Yes, the word 'if' does have meaning in an expression context, and
yes, it has a similar meaning to the 'if' statement, but people don't
parenthesize entire statements. You try that with assignment, you get
an error, and bam, it's obvious that you ran into this particular
case.

ChrisA

Anthony Flury via Python-Dev

unread,
Apr 24, 2018, 11:02:56 AM4/24/18
to pytho...@python.org
On 24/04/18 14:50, Yury Selivanov wrote:
> On Tue, Apr 24, 2018 at 9:46 AM, Nick Coghlan <ncog...@gmail.com> wrote:
>> On 24 April 2018 at 23:38, Yury Selivanov <yseliv...@gmail.com> wrote:
>>> I propose to use the following syntax for assignment expressions:
>>>
>>> ( NAME = expr )
>>>
>>> I know that it was proposed before and this idea was rejected, because
>>> accidentally using '=' in place of '==' is a pain point in
>>> C/C++/JavaScript.
>>>
>>> That said, I believe we can still use this syntax as long as we impose
>>> the following three restrictions on it:
>>>
>>> 1. Only NAME token is allowed as a single target.
>>>
>>> 2. Parenthesis are required.
>>>
>>> 3. Most importantly: it is *not* allowed to mask names in the current
>>> local scope.
>> While I agree this would be unambiguous to a computer, I think for
>> most humans it would be experienced as a confusing set of arcane and
>> arbitrary rules about what "=" means in Python.
> I respectfully disagree. There are no "arcane and confusing rules"
> about "=", it's rather simple:
>
> "=" is always an assignment.
But it isn't - in your proposed syntax :

* *<target> = <value>* is an assignment with no return value
* *(<target> = <value>)* is an assignment with a returned value

  So now '=' is always an assignment, it is an assignment with extra
semantics depending on surrounding syntax.

As discussed previously by others on this exact proposals, you now have
the issue of  confusion when using keyword arguments : *my_func(a = b)*
: clearly that is a call to `my_func' where argument a has the value of
b, but if you want to do an assigment expression when calling the
function you now have to do *my_func((a=b)) -* which frankly looks messy
in my opinion; you get the same issue when you are wanting to do
assignment expressions in tuples.

Using a different operator for assignments which return values avoids
the messy potentially multiple level brackets, and means that the
semantics of an operator depends only on that operator and not on syntax
elements before and after it.

--
--
Anthony Flury
email : *Anthon...@btinternet.com*
Twitter : *@TonyFlury <https://twitter.com/TonyFlury/>*

Yury Selivanov

unread,
Apr 24, 2018, 11:05:23 AM4/24/18
to Chris Angelico, Python-Dev
On Tue, Apr 24, 2018 at 10:56 AM, Chris Angelico <ros...@gmail.com> wrote:
[..]
>> A lot of other questions arise though. PEP 572 proposes:
>>
>> a = 1 # assignment
>> a := 1 # also assignment
>> (a := 1) # also assignment
>> (a = 1) # error, why?
>
> Your third example is just the same as the second, with parentheses
> around it. In most of Python, parentheses (if legal) have no effect
> other than grouping; "a + b * c" is the same thing as "(a + b) * c",
> just done in the other order. The last one is a clear demonstration
> that "=" is a statement, not an expression. Are people confused by
> this sort of thing:
>
> if x > 1:
> print("x is more than 1")
> (if x > 1:)
> print("SyntaxError")

This is a very far-fetched example :)

My point was that when you see lots of '=' and ':=' used at the
statement level, one might try to write "if x = 1" instead of "if x :=
1" -- boom, we have an unexpected SyntaxError for some users.

In my opinion adding *any* assignment expression syntax to Python
*will* create this sort of issues. PEP 572 isn't free of them, my
proposal isn't free of them. My proposal doesn't add a new ':='
operator at the cost of slightly complicating rules around '='. PEP
572 avoids complicating '=', but adds an entirely new form of
assignment.

Yury

Eric Snow

unread,
Apr 24, 2018, 11:05:54 AM4/24/18
to Yury Selivanov, Python-Dev
Thanks for thinking this through, Yury. :)

FWIW, I'm still unconvinced that an assignment expression is worth it.
It's hard to say, though, without seeing how much folks would actually
use it (and I don't have my own time machine unfortunately). IIRC, in
the past several proposed syntax (e.g. decorators) were in the same
boat and in retrospect turned out to be a strongly positive addition
to the language. :)

Comments in-line below.

-eric

On Tue, Apr 24, 2018 at 7:38 AM, Yury Selivanov <yseliv...@gmail.com> wrote:
> I propose to use the following syntax for assignment expressions:
>
> ( NAME = expr )
>
> [snip]
>
> 1. Only NAME token is allowed as a single target.

This seems reasonable and does keep assignment expressions easier for
the reader to find. At the same time, there have been some arguments
elsewhere in favor of tuple unpacking as the other obvious use case.
Presumably that would not be supported under this rule.

> 2. Parenthesis are required.

Similar to rule #1, this would make assignment expressions more
obvious to readers, which is a good thing.

>
> 3. Most importantly: it is *not* allowed to mask names in the current
> local scope.

I was about to leave this counter example for
accidentally-typed-one-equal-sign-instead-of-two bugs:

if (y == 3):
print(y)
# vs.
if (y = 3):
print(y)

Then it dawned on me that your rule #3 solves this. :)

> [snip]
>
> py> f = lambda x: x * 10
> py> [[(y = f(x)), x/y] for x in range(1,5)]
> [[10, 0.1], [20, 0.1], [30, 0.1], [40, 0.1]]
>
> [snip]
>
> I believe that this syntax is the best of both worlds: it allows to
> write succinct code just like PEP 572, but without introducing a new
> ':=' operator.

This is the main point of this alternate proposal, right? It
certainly seems reasonable that we not add another assignment syntax.
On the other hand, having ":=" as a distinct syntax for assignment
expressions is close enough to the existing syntax that it doesn't
really add any real extra burden to readers, while being more
searchable and visually distinct. If we were to add assignment
expressions I'd probably favor ":=".

Regardless, your 3 rules would benefit either syntax. Nick may have a
point that the rules might be an excessive burden, but I don't think
it's too big a deal since the restrictions are few (and align with the
most likely usage) and are limited to syntax so the compiler will be
quick to point mistakes.

Yury Selivanov

unread,
Apr 24, 2018, 11:08:52 AM4/24/18
to Paul Moore, Nick Coghlan, Python-Dev
On Tue, Apr 24, 2018 at 10:49 AM, Paul Moore <p.f....@gmail.com> wrote:
[..]
>>> 3. Most importantly: it is *not* allowed to mask names in the current
>>> local scope.
>>
>> While I agree this would be unambiguous to a computer, I think for
>> most humans it would be experienced as a confusing set of arcane and
>> arbitrary rules about what "=" means in Python.
>
> Also, there's the ambiguity and potential for misreading in the
> opposite direction (accidentally *reading* = as == even though it
> isn't):
>
> if (diff = x - x_base) and (g = gcd(diff, n)) > 1:
> return g

Since 'diff' and 'g' must be new names according to rule (3), those
who read the code will notice that both were not previously bound.
Therefore both are new variables so it can't be a comparison.

Yury

Chris Angelico

unread,
Apr 24, 2018, 11:10:51 AM4/24/18
to python-dev
On Wed, Apr 25, 2018 at 12:54 AM, Anthony Flury via Python-Dev
<pytho...@python.org> wrote:
> As discussed previously by others on this exact proposals, you now have the
> issue of confusion when using keyword arguments : *my_func(a = b)* :
> clearly that is a call to `my_func' where argument a has the value of b, but
> if you want to do an assigment expression when calling the function you now
> have to do *my_func((a=b)) -* which frankly looks messy in my opinion; you
> get the same issue when you are wanting to do assignment expressions in
> tuples.

To be fair, function arguments already follow "practicality beats
purity" in many ways. Let's look at tuples:

x = 1, 2 # fine
x = (1, 2) # fine
x = 1, # fine, though not advisable
x = (1,) # fine

But if you're going to use a tuple literal as a function parameter,
you have to give it extra parens:

f((1, 2)) # one arg, a tuple
f(1, 2) # two args

The comma has multiple meanings, and it has to be disambiguated. The
equals sign would be the same.

I'm still strongly -1 on any proposal to have "=" mean assignment in
any expression context, though. It is WAY too easy for a comparison to
sneakily become an assignment, or to get bizarre syntax errors:

x = 1
if (x = 2): ...

This, according to your proposal, raises SyntaxError - not because a
comparison was wanted and an assignment was made, but because the name
already had a value. And, even worse, this is NOT an error:

x = 1
def f():
if (x = 2):
...

That's a bizarre distinction.

ChrisA

Yury Selivanov

unread,
Apr 24, 2018, 11:11:49 AM4/24/18
to Anthony Flury, Python-Dev
On Tue, Apr 24, 2018 at 10:54 AM, Anthony Flury via Python-Dev
<pytho...@python.org> wrote:
[..]
> As discussed previously by others on this exact proposals, you now have the
> issue of confusion when using keyword arguments : *my_func(a = b)* :
> clearly that is a call to `my_func' where argument a has the value of b, but
> if you want to do an assigment expression when calling the function you now
> have to do *my_func((a=b)) -* which frankly looks messy in my opinion; you
> get the same issue when you are wanting to do assignment expressions in
> tuples.

Well, `my_func(a=(b:=foo))` or `my_func(b:=foo)` are also barely
readable to my eye. My expectation is that users won't use any form
of assignment expressions in function calls, it's painful with both
proposals.

Yury

Paul Moore

unread,
Apr 24, 2018, 11:14:45 AM4/24/18
to Yury Selivanov, Nick Coghlan, Python-Dev
On 24 April 2018 at 15:58, Yury Selivanov <yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 10:49 AM, Paul Moore <p.f....@gmail.com> wrote:
> [..]
>>>> 3. Most importantly: it is *not* allowed to mask names in the current
>>>> local scope.
>>>
>>> While I agree this would be unambiguous to a computer, I think for
>>> most humans it would be experienced as a confusing set of arcane and
>>> arbitrary rules about what "=" means in Python.
>>
>> Also, there's the ambiguity and potential for misreading in the
>> opposite direction (accidentally *reading* = as == even though it
>> isn't):
>>
>> if (diff = x - x_base) and (g = gcd(diff, n)) > 1:
>> return g
>
> Since 'diff' and 'g' must be new names according to rule (3), those
> who read the code will notice that both were not previously bound.
> Therefore both are new variables so it can't be a comparison.

That was essentially my point, though - I can no longer read that line
of code in isolation from the surrounding context. Consider something
like a github PR review screen, where surrounding unchanged code is
frequently hidden.

Anyway, we can agree to differ on this - I don't like this idea and
I'd personally find it hard to read, but as you've already pointed
out, this is all extremely subjective.
Paul

Chris Angelico

unread,
Apr 24, 2018, 11:17:14 AM4/24/18
to Python-Dev
On Wed, Apr 25, 2018 at 1:03 AM, Yury Selivanov <yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 10:56 AM, Chris Angelico <ros...@gmail.com> wrote:
> [..]
>>> A lot of other questions arise though. PEP 572 proposes:
>>>
>>> a = 1 # assignment
>>> a := 1 # also assignment
>>> (a := 1) # also assignment
>>> (a = 1) # error, why?
>>
>> Your third example is just the same as the second, with parentheses
>> around it. In most of Python, parentheses (if legal) have no effect
>> other than grouping; "a + b * c" is the same thing as "(a + b) * c",
>> just done in the other order. The last one is a clear demonstration
>> that "=" is a statement, not an expression. Are people confused by
>> this sort of thing:
>>
>> if x > 1:
>> print("x is more than 1")
>> (if x > 1:)
>> print("SyntaxError")
>
> This is a very far-fetched example :)

Heh, yes it is. But my point is that the parens are not creating a
weird situation here. They're just showcasing a distinction: one of
these is a statement, the other an expression. Which is the entire
point of the different operator - one is a syntactic feature of a
statement that creates one or more name bindings, the other is a
binary operator which results in a name binding as well as a value.

ChrisA

Chris Angelico

unread,
Apr 24, 2018, 11:17:55 AM4/24/18
to Python-Dev
On Wed, Apr 25, 2018 at 12:58 AM, Yury Selivanov
<yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 10:49 AM, Paul Moore <p.f....@gmail.com> wrote:
> [..]
>>>> 3. Most importantly: it is *not* allowed to mask names in the current
>>>> local scope.
>>>
>>> While I agree this would be unambiguous to a computer, I think for
>>> most humans it would be experienced as a confusing set of arcane and
>>> arbitrary rules about what "=" means in Python.
>>
>> Also, there's the ambiguity and potential for misreading in the
>> opposite direction (accidentally *reading* = as == even though it
>> isn't):
>>
>> if (diff = x - x_base) and (g = gcd(diff, n)) > 1:
>> return g
>
> Since 'diff' and 'g' must be new names according to rule (3), those
> who read the code will notice that both were not previously bound.
> Therefore both are new variables so it can't be a comparison.

That would not be true if this code were in a loop. Or do you have a
different definition of "not previously bound" that is actually a
syntactic feature? For instance:

if (x = 1):
x = 2

Legal? Not legal?

ChrisA

Steven D'Aprano

unread,
Apr 24, 2018, 11:20:52 AM4/24/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 09:50:34AM -0400, Yury Selivanov wrote:
> On Tue, Apr 24, 2018 at 9:46 AM, Nick Coghlan <ncog...@gmail.com> wrote:
> > On 24 April 2018 at 23:38, Yury Selivanov <yseliv...@gmail.com> wrote:
> >> I propose to use the following syntax for assignment expressions:
> >>
> >> ( NAME = expr )
> >>
> >> I know that it was proposed before and this idea was rejected, because
> >> accidentally using '=' in place of '==' is a pain point in
> >> C/C++/JavaScript.
> >>
> >> That said, I believe we can still use this syntax as long as we impose
> >> the following three restrictions on it:
> >>
> >> 1. Only NAME token is allowed as a single target.
> >>
> >> 2. Parenthesis are required.

There are many places where I would use parentheses even if they are
not required, but being forced to use them when they're not and I don't
want them is ugly.

I also question why you think this will help prevent accidentally
writing = when you meant == (equality). Have you never written something
like this?

if (x == y) or (a > b): ...

Yes, I know the parens are not strictly needed, since the precedence of
`or` is lower than the comparison operators. But still, especially for
complex comparisons, a few extra (round) brackets can improve
readability.

So now we have:

if (x = y) or (a > b): ... # oops


But the biggest problem with this is that it is ambiguous to the human
reader. At a glance, I'm likely to read x=y in an expression as
equality. If I notice that it is a single = sign, I'm never going to be
sure whether it was a mistake or intentional until I study the rest of
the function minutely.

The benefit of := is that if I see it, I can be pretty sure it was not a
typo. It is hard to mistype == as := by accident, and they are visually
distinct enough that I am not going to misread := as == .


> >> 3. Most importantly: it is *not* allowed to mask names in the current
> >> local scope.

That means you can't rebind existing variables. That means you can't
rebind to the same variable in a loop.

I believe that one of the most important use-cases for binding-
expression syntax is while loops, like this modified example taken from
PEP 572 version 3:

while (data = sock.read()):
print("Received data:", data)

If you prohibit re-binding data, that prohibits cases like this, or even
using it inside a loop:

for value in sequence:
process(arg, (item = expression), item+1)


Your suggestion to prohibit rebinding variables effectively makes them
so crippled as to be useless to me.


> > While I agree this would be unambiguous to a computer, I think for
> > most humans it would be experienced as a confusing set of arcane and
> > arbitrary rules about what "=" means in Python.
>
> I respectfully disagree. There are no "arcane and confusing rules"
> about "=", it's rather simple:
>
> "=" is always an assignment.

Why is this allowed?

x = 1 # both are statement forms
x = 2

but this is prohibited?

x = 1
(x = 2) # no rebinding is allowed

and even more confusing, this is allowed!

(x = 1) # x doesn't exist yet, so it is allowed
x = 2 # statement assignment is allowed to rebind


By the way, the check for existing variables cannot help to be either
incomplete or incorrect if you try to do it at compile time:


from module import *
(x = 2) # allowed or not allowed?


If you don't like wild-card imports, how about this:

if random.random() > 0.5:
spam = 1
else:
eggs = 1
(spam = 2) # allowed or not? no way to tell at compile time


But doing the rebinding/shadowing check at runtime will slow down
binding expressions, and lead to even more arcane and confusing results:

it = iter("abc")
while (obj = next(it)):
print(obj)


will print "a" on the first loop, but then raise an exception on
the second time loop as obj now exists.



--
Steve

Yury Selivanov

unread,
Apr 24, 2018, 11:21:16 AM4/24/18
to Chris Angelico, python-dev
On Tue, Apr 24, 2018 at 11:07 AM, Chris Angelico <ros...@gmail.com> wrote:
[..]

> x = 1
> if (x = 2): ...
>
> This, according to your proposal, raises SyntaxError - not because a
> comparison was wanted and an assignment was made, but because the name
> already had a value. And, even worse, this is NOT an error:

Yes, because I'm trying to think about this from a pragmatic side of
things. My question to myself: "what syntax could I use that would
prevent me from making '=' vs '==' mistake when I code?" To me, the
answer is that I usually want to compare local variables.

When I compare to variables from outer scopes they *usually* are on
the *right* side of '=='.

>
> x = 1
> def f():
> if (x = 2):
> ...
>
> That's a bizarre distinction.

Chris, FWIW I'm trying to avoid using 'bizarre', 'arcane' etc with
regards to PEP 572 or any proposal, really. For example, I,
personally, find ':=' bizarre, but it's subjective and it's
unproductive to say that.

Yury

Christoph Groth

unread,
Apr 24, 2018, 11:23:46 AM4/24/18
to pytho...@python.org
Yury Selivanov wrote:
> I propose to use the following syntax for assignment expressions:
>
> ( NAME = expr )

Yury, did you notice the subthread [1] that discusses the merits and
problems of the same idea (except for your point 3)?

[1] https://mail.python.org/pipermail/python-dev/2018-April/152868.html

Nick Coghlan

unread,
Apr 24, 2018, 11:33:14 AM4/24/18
to Eric Snow, Python-Dev
On 25 April 2018 at 00:54, Eric Snow <ericsnow...@gmail.com> wrote:
> Regardless, your 3 rules would benefit either syntax. Nick may have a
> point that the rules might be an excessive burden, but I don't think
> it's too big a deal since the restrictions are few (and align with the
> most likely usage) and are limited to syntax so the compiler will be
> quick to point mistakes.

I think the "single name target only" rule should be in place no
matter the syntax for the name binding operator itself.

I don't mind too much either way on the mandatory parentheses question
(it's certainly an easy option to actively discourage use of binding
expressions as a direct alternative to assignment statements, but as
with the single-name-only rule, it's independent of the choice of
syntax)

I *do* think the "no name rebinding except in a while loop header"
restriction would be annoying for the if/elif use case and the while
use case:

while (item = get_item()) is not first_delimiter:
# First processing loop
while (item = get_item()) is not second_delimiter:
# Second processing loop
# etc...

if (target = get_first_candidate()) is not None:
...
elif (target = get_second_candidate()) is not None:
...
elif (target = get_third_candidate()) is not None:
...

And *that* rule is unique to the "=" spelling, since for other
proposals "lhs = rhs" in an expression is *always* a syntax error, and
you have to resolve the ambiguity in intent explicitly by either
adding a second "=" (to request equality comparison), or else some
other leading symbol (to request a binding expression).

Cheers,
Nick.

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

Yury Selivanov

unread,
Apr 24, 2018, 11:36:01 AM4/24/18
to Steven D'Aprano, Python-Dev
On Tue, Apr 24, 2018 at 11:15 AM, Steven D'Aprano <st...@pearwood.info> wrote:
[..]

>> >> 3. Most importantly: it is *not* allowed to mask names in the current
>> >> local scope.
>
> That means you can't rebind existing variables. That means you can't
> rebind to the same variable in a loop.

No, it doesn't. The check is performed during compile phase, and
Python does not unroll loops. Anyways, read below.

> I believe that one of the most important use-cases for binding-
> expression syntax is while loops, like this modified example taken from
> PEP 572 version 3:
>
> while (data = sock.read()):
> print("Received data:", data)
>
> If you prohibit re-binding data, that prohibits cases like this, or even
> using it inside a loop:
>
> for value in sequence:
> process(arg, (item = expression), item+1)

No it doesn't. symtable in Python works differently. I encourage you
to test my reference implementation:

py> for val in [1, 2, 3]:
... print((item=val), item+1)
...
1 2
2 3
3 4

> Why is this allowed?
>
> x = 1 # both are statement forms
> x = 2
>
> but this is prohibited?
>
> x = 1
> (x = 2) # no rebinding is allowed
>
> and even more confusing, this is allowed!
>
> (x = 1) # x doesn't exist yet, so it is allowed
> x = 2 # statement assignment is allowed to rebind

These all are very limited code snippets that you're unlikely to see
in real code. I can write (and I did in this thread) a bunch of
examples of where PEP 572 is also inconsistent.

Yury

Steven D'Aprano

unread,
Apr 24, 2018, 11:39:04 AM4/24/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 11:03:35AM -0400, Yury Selivanov wrote:

> My point was that when you see lots of '=' and ':=' used at the
> statement level, one might try to write "if x = 1" instead of "if x :=
> 1" -- boom, we have an unexpected SyntaxError for some users.

That's a *good* thing. They will then learn not to write x = 1 as an
expression.

Also, if I write lots of x := 1 binding-expressions as statements, my
code is bad and deserves to fail code-review. But why would I write the
extra colon (one character, two key-presses) to use

x := 1

as a statement, when x = 1 will work? That's a sure sign that I don't
know what I'm doing. (Or that I desperately wish I was writing Pascal.)

I don't think we need worry about this. The sort of code that is filled
with binding-expressions used as statements will almost certainly be so
un-Pythonic and ugly in many other ways, that this won't make any
difference.


> In my opinion adding *any* assignment expression syntax to Python
> *will* create this sort of issues. PEP 572 isn't free of them, my
> proposal isn't free of them. My proposal doesn't add a new ':='
> operator at the cost of slightly complicating rules around '='. PEP
> 572 avoids complicating '=', but adds an entirely new form of
> assignment.

Indeed. That is true: either way, we introduce complexity into the
language. (But that will allow us to reduce complexity in *our* code.)
Given that increasing complexity is inevitable regardless of whether we
choose PEP 572 or your suggestion, it is better to choose the option
which keeps binding-expressions and assignment statements separate,
since they are two different concepts.



--
Steve

Yury Selivanov

unread,
Apr 24, 2018, 11:40:27 AM4/24/18
to Nick Coghlan, Python-Dev
On Tue, Apr 24, 2018 at 11:31 AM, Nick Coghlan <ncog...@gmail.com> wrote:
> On 25 April 2018 at 00:54, Eric Snow <ericsnow...@gmail.com> wrote:
>> Regardless, your 3 rules would benefit either syntax. Nick may have a
>> point that the rules might be an excessive burden, but I don't think
>> it's too big a deal since the restrictions are few (and align with the
>> most likely usage) and are limited to syntax so the compiler will be
>> quick to point mistakes.
>
> I think the "single name target only" rule should be in place no
> matter the syntax for the name binding operator itself.
>
> I don't mind too much either way on the mandatory parentheses question
> (it's certainly an easy option to actively discourage use of binding
> expressions as a direct alternative to assignment statements, but as
> with the single-name-only rule, it's independent of the choice of
> syntax)

Mandatory parenthesis around `(name := expr)` would at least solve the
problem of users mixing up '=' and ':=' in statements.

>
> I *do* think the "no name rebinding except in a while loop header"
> restriction would be annoying for the if/elif use case and the while
> use case:
>
> while (item = get_item()) is not first_delimiter:
> # First processing loop
> while (item = get_item()) is not second_delimiter:
> # Second processing loop
> # etc...
>
> if (target = get_first_candidate()) is not None:
> ...
> elif (target = get_second_candidate()) is not None:
> ...
> elif (target = get_third_candidate()) is not None:
> ...

Yes, it would force users to come up with better names *iff* they want
to use this new sugar:

if (first_target = get_first_candidate()) ...
elif (second_target = get_second_candidate()) ...

Yury

Chris Angelico

unread,
Apr 24, 2018, 11:42:02 AM4/24/18
to python-dev
On Wed, Apr 25, 2018 at 1:15 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> By the way, the check for existing variables cannot help to be either
> incomplete or incorrect if you try to do it at compile time:
>
>
> from module import *
> (x = 2) # allowed or not allowed?
>
>
> If you don't like wild-card imports, how about this:
>
> if random.random() > 0.5:
> spam = 1
> else:
> eggs = 1
> (spam = 2) # allowed or not? no way to tell at compile time
>
>
> But doing the rebinding/shadowing check at runtime will slow down
> binding expressions, and lead to even more arcane and confusing results:
>
> it = iter("abc")
> while (obj = next(it)):
> print(obj)
>
>
> will print "a" on the first loop, but then raise an exception on
> the second time loop as obj now exists.

On re-thinking this, I think the distinction IS possible, but (a) only
in function/class scope, not at global; and (b) would be defined in
terms of lexical position, not run-time. For instance:

def f():
(a = 1) # Legal; 'a' has not been used yet
a = 2 # doesn't change that

def f(a):
(a = 1) # Invalid - 'a' has been used already

def f():
while (a = get_next()): # Legal
...

This could be handled in the symbol collection pass; if the name
already exists in the function's locals, it's disallowed. But I still
stand by my statement that this creates bizarre cases, and yes, I know
that that word is subjective (just like "readable", "intuitive", and
"sensible"). The rules as given sound like they would make great
linter rules and terrible syntax rules. They are closely aligned with
the OP's experience and usage patterns - which means that, as a
personal linter, they could marvellously assist in catching bugs. You
could have personal (or organization-wide) linter rules disallowing
"class foo:" with a lower-case name, and disallowing the rebinding of
any name in ALL_CAPS, but I would not want either rule codified into
language syntax.

ChrisA

Steven D'Aprano

unread,
Apr 24, 2018, 11:46:34 AM4/24/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 11:05:57AM -0400, Yury Selivanov wrote:

> Well, `my_func(a=(b:=foo))` or `my_func(b:=foo)` are also barely
> readable to my eye.

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:

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


> My expectation is that users won't use any form
> of assignment expressions in function calls, it's painful with both
> proposals.

If binding-expressions are accepted into the language, I will certainly
use them in function calls, *if and when appropriate*. I don't expect it
will be common, but I'm sure it will happen.



--
Steve

Yury Selivanov

unread,
Apr 24, 2018, 11:46:53 AM4/24/18
to Steven D'Aprano, Python-Dev
On Tue, Apr 24, 2018 at 11:27 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Tue, Apr 24, 2018 at 11:03:35AM -0400, Yury Selivanov wrote:
>
>> My point was that when you see lots of '=' and ':=' used at the
>> statement level, one might try to write "if x = 1" instead of "if x :=
>> 1" -- boom, we have an unexpected SyntaxError for some users.
>
> That's a *good* thing. They will then learn not to write x = 1 as an
> expression.
>
> Also, if I write lots of x := 1 binding-expressions as statements, my
> code is bad and deserves to fail code-review. But why would I write the
> extra colon (one character, two key-presses) to use
>
> x := 1
>
> as a statement, when x = 1 will work? That's a sure sign that I don't
> know what I'm doing. (Or that I desperately wish I was writing Pascal.)

In JavaScript there's a new backticks syntax for string—their variant
of f-strings. I'm seeing a lot of JS coders that use backticks
everywhere, regardless if there's formatting in them or not. The
result is that some JS code in popular libraries has now *three*
different string literal syntaxes separated by one line of code. It
looks weird. I expect to see something similar in Python code if we
adapt ':='. I don't think the language will benefit from this.

FWIW I'm fine with keeping the status quo and not adding new syntax at all.

Yury

Yury Selivanov

unread,
Apr 24, 2018, 11:49:49 AM4/24/18
to Chris Angelico, python-dev
On Tue, Apr 24, 2018 at 11:28 AM, Chris Angelico <ros...@gmail.com> wrote:

> On re-thinking this, I think the distinction IS possible, but (a) only
> in function/class scope, not at global; and (b) would be defined in
> terms of lexical position, not run-time. For instance:
>
> def f():
> (a = 1) # Legal; 'a' has not been used yet
> a = 2 # doesn't change that
>
> def f(a):
> (a = 1) # Invalid - 'a' has been used already
>
> def f():
> while (a = get_next()): # Legal
> ...

Now *this* is a weird rule. Moving functions around files would become
impossible.

Please experiment with my reference implementation, it already
implements my proposal in full. Loops and inline assignments work as
expected in it.

Yury

Ethan Furman

unread,
Apr 24, 2018, 11:52:44 AM4/24/18
to pytho...@python.org
On 04/24/2018 08:19 AM, Yury Selivanov wrote:

> Yes, because I'm trying to think about this from a pragmatic side of
> things. My question to myself: "what syntax could I use that would
> prevent me from making '=' vs '==' mistake when I code?" To me, the
> answer is that I usually want to compare local variables.

I think we need to disambiguate between typo-typos and thinko-typos. I suspect the vast majority of the '=' bugs are
not due to the programmer /thinking/ the wrong operation, but of their hands/keyboards not /entering/ the right symbols;
having a legal operator ("==") degrade into another legal operator ("=") that looks similar but means incredibly
different things is a trap that we should not add to Python.

You might say that we have the same problems with ">=", "<=", and "!=". We don't with "!=" because neither "!" nor "="
can stand alone and would fail. We only have it partially with "<=" and ">=" because missing the angle bracket results
in failure, but missing the "=" results in a working statement -- but that statement is still the same type of operation
and is easier to debug when boundary cases fail.

> When I compare to variables from outer scopes they *usually* are on
> the *right* side of '=='.

You mean something like

if 2 == x:

? I never write code like that, and I haven't seen it, either.

--
~Ethan~

Ethan Furman

unread,
Apr 24, 2018, 11:55:59 AM4/24/18
to pytho...@python.org
On 04/24/2018 08:35 AM, Yury Selivanov wrote:

> Yes, it would force users to come up with better names *iff* they want
> to use this new sugar:
>
> if (first_target = get_first_candidate()) ...
> elif (second_target = get_second_candidate()) ...

And then the common code below that only cares about a target being found now has to do a

item = first_target or second_target or ...

I don't see that as an improvement.

--
~Ethan~

Yury Selivanov

unread,
Apr 24, 2018, 11:57:14 AM4/24/18
to Steven D'Aprano, Python-Dev
On Tue, Apr 24, 2018 at 11:34 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Tue, Apr 24, 2018 at 11:05:57AM -0400, Yury Selivanov wrote:
>
>> Well, `my_func(a=(b:=foo))` or `my_func(b:=foo)` are also barely
>> readable to my eye.
>
> 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:
>
> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))

Again, this is very subjective, but this code would fail my code review :)

Don't you find

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

to be more readable?

IMHO this example is why we shouldn't implement any form of assignment
expressions in Python :)

Yury

Nick Coghlan

unread,
Apr 24, 2018, 11:59:05 AM4/24/18
to Yury Selivanov, Python-Dev
On 25 April 2018 at 01:35, Yury Selivanov <yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 11:31 AM, Nick Coghlan <ncog...@gmail.com> wrote:
>> I *do* think the "no name rebinding except in a while loop header"
>> restriction would be annoying for the if/elif use case and the while
>> use case:
>>
>> while (item = get_item()) is not first_delimiter:
>> # First processing loop
>> while (item = get_item()) is not second_delimiter:
>> # Second processing loop
>> # etc...
>>
>> if (target = get_first_candidate()) is not None:
>> ...
>> elif (target = get_second_candidate()) is not None:
>> ...
>> elif (target = get_third_candidate()) is not None:
>> ...
>
> Yes, it would force users to come up with better names *iff* they want
> to use this new sugar:
>
> if (first_target = get_first_candidate()) ...
> elif (second_target = get_second_candidate()) ...

Sorry, I didn't make the intended nature of that example clear:

if (target = get_first_candidate()) is not None:
... # Any setup code specific to this kind of target
elif (target = get_second_candidate()) is not None:
... # Any setup code specific to this kind of target
elif (target = get_third_candidate()) is not None:
... # Any setup code specific to this kind of target
else:
raise RuntimeError("No valid candidate found")
# Common code using target goes here

Using a separate name in each branch wouldn't solve the problem of
binding the *same* name for the common code to use later - you'd have
to put a "target = candidate_n" bit of boilerplate in each branch.

Cheers,
Nick.

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

Chris Angelico

unread,
Apr 24, 2018, 12:02:05 PM4/24/18
to Python-Dev
On Wed, Apr 25, 2018 at 1:49 AM, Yury Selivanov <yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 11:34 AM, Steven D'Aprano <st...@pearwood.info> wrote:
>> On Tue, Apr 24, 2018 at 11:05:57AM -0400, Yury Selivanov wrote:
>>
>>> Well, `my_func(a=(b:=foo))` or `my_func(b:=foo)` are also barely
>>> readable to my eye.
>>
>> 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:
>>
>> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
>
> Again, this is very subjective, but this code would fail my code review :)
>
> Don't you find
>
> buf = [None] * get_size()
> my_func(arg, buffer=buf, size=len(buf))
>
> to be more readable?

Only if 'buf' is going to be used elsewhere. I'd be looking down below
for some other use of 'buf'. Technically the same could be true of the
inline assignment, but it makes more sense for a "this statement only"
name binding to be within that statement, not broken out and placed
above it as another operation at equal importance.

ChrisA

Yury Selivanov

unread,
Apr 24, 2018, 12:02:23 PM4/24/18
to Ethan Furman, Python-Dev
On Tue, Apr 24, 2018 at 11:51 AM, Ethan Furman <et...@stoneleaf.us> wrote:

>> When I compare to variables from outer scopes they *usually* are on
>> the *right* side of '=='.
>
>
> You mean something like
>
> if 2 == x:
>
> ? I never write code like that, and I haven't seen it, either.

Hm. I mean this:

const = 'something'

def foo(arg):
if arg == const:
do something

Note that "const" is on the right side of "==".

Would you write this as

def foo(arg):
if const == arg:

? ;)

Yury

Ethan Furman

unread,
Apr 24, 2018, 12:09:15 PM4/24/18
to Python-Dev
On 04/24/2018 08:56 AM, Yury Selivanov wrote:
> On Tue, Apr 24, 2018 at 11:51 AM, Ethan Furman <et...@stoneleaf.us> wrote:
>
>>> When I compare to variables from outer scopes they *usually* are on
>>> the *right* side of '=='.
>>
>>
>> You mean something like
>>
>> if 2 == x:
>>
>> ? I never write code like that, and I haven't seen it, either.
>
> Hm. I mean this:
>
> const = 'something'
>
> def foo(arg):
> if arg == const:
> do something
>
> Note that "const" is on the right side of "==".
>
> Would you write this as
>
> def foo(arg):
> if const == arg:
>
> ? ;)

Heh, no.

But I do write this:

def wrapper(func, some_value):
value_I_want = process(some_value)
def wrapped(*args, **kwds):
if value_I_want == 42:
...

--
~Ethan~

Chris Angelico

unread,
Apr 24, 2018, 12:10:54 PM4/24/18
to Python-Dev
On Wed, Apr 25, 2018 at 1:56 AM, Yury Selivanov <yseliv...@gmail.com> wrote:
> On Tue, Apr 24, 2018 at 11:51 AM, Ethan Furman <et...@stoneleaf.us> wrote:
>
>>> When I compare to variables from outer scopes they *usually* are on
>>> the *right* side of '=='.
>>
>>
>> You mean something like
>>
>> if 2 == x:
>>
>> ? I never write code like that, and I haven't seen it, either.
>
> Hm. I mean this:
>
> const = 'something'
>
> def foo(arg):
> if arg == const:
> do something
>
> Note that "const" is on the right side of "==".
>
> Would you write this as
>
> def foo(arg):
> if const == arg:
>
> ? ;)

That's assuming the global is a constant. What if it's a mode-setting marker?

def foo(arg):
...
if output == "verbose" or (output != "quiet" and error_count):
print("Arg foo'd", arg)
print("Errors found:", error_count)

Then I would definitely put the variable first. And I know a lot of
people who would parenthesize the first condition in this.

ChrisA

Yury Selivanov

unread,
Apr 24, 2018, 12:13:24 PM4/24/18
to Chris Angelico, Python-Dev
On Tue, Apr 24, 2018 at 11:58 AM, Chris Angelico <ros...@gmail.com> wrote:
> On Wed, Apr 25, 2018 at 1:49 AM, Yury Selivanov <yseliv...@gmail.com> wrote:
>> On Tue, Apr 24, 2018 at 11:34 AM, Steven D'Aprano <st...@pearwood.info> wrote:
[..]
>>> 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:
>>>
>>> my_func(arg, buffer=(buf := [None]*get_size()), size=len(buf))
>>
>> Again, this is very subjective, but this code would fail my code review :)
>>
>> Don't you find
>>
>> buf = [None] * get_size()
>> my_func(arg, buffer=buf, size=len(buf))
>>
>> to be more readable?
>
> Only if 'buf' is going to be used elsewhere. I'd be looking down below
> for some other use of 'buf'. Technically the same could be true of the
> inline assignment, but it makes more sense for a "this statement only"
> name binding to be within that statement, not broken out and placed
> above it as another operation at equal importance.

Well, you can use empty lines to visually indicate that 'buf' is
related to the call.

Moreover, 'buf' is still available to the code below that code and
sometimes be used there. You can't tell for sure until you glance over
the entire file/function. PEP 572 does not implement any sort of
sub-scoping.

Yury

Yury Selivanov

unread,
Apr 24, 2018, 12:13:54 PM4/24/18
to Ethan Furman, Python-Dev
On Tue, Apr 24, 2018 at 12:03 PM, Ethan Furman <et...@stoneleaf.us> wrote:
[..]
> But I do write this:
>
> def wrapper(func, some_value):
> value_I_want = process(some_value)
> def wrapped(*args, **kwds):
> if value_I_want == 42:
> ...

But this pattern is more rare than comparing local variables. That's
the point I'm trying to use. Besides, to make it an assignment
expression under my proposal you would need to use parens. Which makes
it even less likely that you confuse '=' and '=='.

Yury

Steven D'Aprano

unread,
Apr 24, 2018, 12:29:58 PM4/24/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 10:58:24AM -0400, Yury Selivanov wrote:

> Since 'diff' and 'g' must be new names according to rule (3), those
> who read the code will notice that both were not previously bound.

How am I supposed to notice that they've never been bound without
carefully reading through the rest of the function in detail, checking
every single expression and statement?

And besides, you have already established that there are exceptions to
the rule "names must be new names". For example, in loops.

What other exceptions are there?


--
Steve

Steven D'Aprano

unread,
Apr 24, 2018, 12:32:52 PM4/24/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 11:25:58AM -0400, Yury Selivanov wrote:

> No, it doesn't. The check is performed during compile phase, and
> Python does not unroll loops. Anyways, read below.

What does unrolling loops have to do with anything? And besides, loop
unrolling is an implementation detail -- maybe Python will unroll loops,
maybe it won't.

If you insist that the check is only done at compile time, then your
description is wrong and your rule that "it is *not* allowed to mask
names in the current local scope" is false. It *is* allowed to shadow
names in the local scope, but only names that cannot be determined at
compile-time.

from math import *
process(arg, (pi = 1), pi+1) # allowed

That's more and worse complexity.

And what about masking names in the class, nonlocal, global and
builtin scopes? Even more complexity and inconsistent behaviour!

def function():
global a
a = b = 1
process(arg, (a = 2), a+1) # allowed
process(arg, (b = 2), b+1) # not allowed



> > I believe that one of the most important use-cases for binding-
> > expression syntax is while loops, like this modified example taken from
> > PEP 572 version 3:
> >
> > while (data = sock.read()):
> > print("Received data:", data)
> >
> > If you prohibit re-binding data, that prohibits cases like this, or even
> > using it inside a loop:
> >
> > for value in sequence:
> > process(arg, (item = expression), item+1)
>
> No it doesn't. symtable in Python works differently. I encourage you
> to test my reference implementation:
>
> py> for val in [1, 2, 3]:
> ... print((item=val), item+1)
> ...
> 1 2
> 2 3
> 3 4

Then your description is false: the assignment in the second time around
the loop is masking the value that was set the first time around the
loop. I should be able to unroll the loop by hand, and the code should
still work:


val = 1
print((item=val), item+1)
val = 2
print((item=val), item+1)
val = 3
print((item=val), item+1)


Does your reference implementation allow that? If not, then you have
added yet another inconsistency and obscure rule to be learned: using
assignment expressions will break loop unrolling even if you do it by
hand.

If it *does* allow that, then so much for your claim that you cannot
mask existing variables. It can.


> > Why is this allowed?
> >
> > x = 1 # both are statement forms
> > x = 2
> >
> > but this is prohibited?
> >
> > x = 1
> > (x = 2) # no rebinding is allowed
> >
> > and even more confusing, this is allowed!
> >
> > (x = 1) # x doesn't exist yet, so it is allowed
> > x = 2 # statement assignment is allowed to rebind
>
> These all are very limited code snippets that you're unlikely to see
> in real code.

Oh come on now Yury, please be reasonable. They're only *sketches* of
more realistic code. Of course I'm not actually going to write something
like

x = 1
(x = 2)

but do you really need me to take the time and effort to come up with a
more realistic (and therefore complex) example? Okay.


# allowed
mo = re.match(HEADER_PATTERN, string)
if mo:
process_header(mo)
... # much later
mo = re.match(FOOTER_PATTERN, string)
if mo:
process_footer(no)



# not allowed
mo = re.match(HEADER_PATTERN, string)
if mo:
process_header(mo)
... # much later
if (mo = re.match(FOOTER_PATTERN, string)): # SyntaxError
process_footer(no)



You stated that 'There are no "arcane and confusing rules" about "=",
it's rather simple' but every time we look closely at it, the rules seem
to get more arcane and confusing.

- why is it okay to mask nonlocal, global, class and builtin
names, but not local?

- for module-level code, how is the compiler supposed to determine
the local names in the face of wildcard imports?

- why is it a syntax error to assign to a name which is not
actually used?

# not allowed
if "a".upper() == "XYZ"[-1].lower():
spam = "this is dead code and will never happen"
process(arg, (spam=expression), spam+1) # syntax error

# but this is allowed
if "a".upper() == "XYZ"[-1].lower():
spam = "this is dead code and will never happen"
spam = expression
process(arg, spam, spam+1)


- why can you *sometimes* mask existing local variables, if
they are used in a loop, but not *other* local variables?

- how does this stop *me*, the human reader, from misreading
(name=expression) as an equality test?



--
Steve

Chris Angelico

unread,
Apr 24, 2018, 12:38:14 PM4/24/18
to python-dev
On Wed, Apr 25, 2018 at 2:28 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Tue, Apr 24, 2018 at 10:58:24AM -0400, Yury Selivanov wrote:
>
>> Since 'diff' and 'g' must be new names according to rule (3), those
>> who read the code will notice that both were not previously bound.
>
> How am I supposed to notice that they've never been bound without
> carefully reading through the rest of the function in detail, checking
> every single expression and statement?
>
> And besides, you have already established that there are exceptions to
> the rule "names must be new names". For example, in loops.
>
> What other exceptions are there?
>

Yuri is talking about "new" in the syntactic sense. A new name is one
which, reading lexically through the code, has not yet been assigned
to in the function. (I don't know what happens with global/nonlocal
declarations.) Loops have a single point at which the name is assigned
to. This has a single point where the name is assigned, too, even
though you'll never hit it:

def f(x):
if x is not x:
y = 1
print(y) # UnboundLocalError

While I disagree with the proposal, it is at least sane from the
compiler's POV. I don't think it makes sense from a human's POV, but
it's internally consistent.

ChrisA

Steven D'Aprano

unread,
Apr 24, 2018, 12:41:09 PM4/24/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 11:35:20AM -0400, Yury Selivanov wrote:

> Yes, it would force users to come up with better names *iff* they want
> to use this new sugar:
>
> if (first_target = get_first_candidate()) ...
> elif (second_target = get_second_candidate()) ...

They're not better names. Adding "first_" and "second_" prefixes are
just meaningless waffle added to the name "target" to satisfy the
compiler so it doesn't complain about reusing the name.

And it is a clear inconsistency with = as a statement and = as an
expression:

# allowed
target = get_first_candidate()
if target:
...
else:
target = get_second_candidate()
if target: ...


# refactor, and we get a syntax error
if (target = get_first_candidate()):
...
elif (target = get_second_candidate()):
...


And I cannot even begin to guess whether this will be allowed or not:

if (target = get_first_candidate()):
...
while (target = get_second_candidate()):
...


--
Steve

Chris Angelico

unread,
Apr 24, 2018, 12:45:43 PM4/24/18
to python-dev
On Wed, Apr 25, 2018 at 2:23 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Tue, Apr 24, 2018 at 11:25:58AM -0400, Yury Selivanov wrote:
>
>> No, it doesn't. The check is performed during compile phase, and
>> Python does not unroll loops. Anyways, read below.
>
> What does unrolling loops have to do with anything? And besides, loop
> unrolling is an implementation detail -- maybe Python will unroll loops,
> maybe it won't.
>
> If you insist that the check is only done at compile time, then your
> description is wrong and your rule that "it is *not* allowed to mask
> names in the current local scope" is false. It *is* allowed to shadow
> names in the local scope, but only names that cannot be determined at
> compile-time.
>
> from math import *
> process(arg, (pi = 1), pi+1) # allowed
>
> That's more and worse complexity.

That's not allowed at local scope (at least, it's not allowed at
function scope - is there any other "local scope" at which it is
allowed?). Not sure if you can craft equivalent shenanigans with
'exec'.

ChrisA

Steven D'Aprano

unread,
Apr 24, 2018, 12:59:38 PM4/24/18
to pytho...@python.org
On Wed, Apr 25, 2018 at 02:42:08AM +1000, Chris Angelico wrote:

> > from math import *
> > process(arg, (pi = 1), pi+1) # allowed


> That's not allowed at local scope (at least, it's not allowed at
> function scope - is there any other "local scope" at which it is
> allowed?).

Of course: local just means the current scope, wherever you happen to
be. Inside a function, local is the current function scope. Inside a
class body, local is the class body scope. At the top level of the
module, local means the module scope (and locals() returns the same dict
as globals()).

If Yury means for this "cannot mask existing variables" to only operate
inside functions, that means that you can mask existing variables if you
use assignment expressions in class bodies or top-level module code.


--
Steve

Chris Angelico

unread,
Apr 24, 2018, 1:25:56 PM4/24/18
to python-dev
On Wed, Apr 25, 2018 at 2:57 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Wed, Apr 25, 2018 at 02:42:08AM +1000, Chris Angelico wrote:
>
>> > from math import *
>> > process(arg, (pi = 1), pi+1) # allowed
>
>
>> That's not allowed at local scope (at least, it's not allowed at
>> function scope - is there any other "local scope" at which it is
>> allowed?).
>
> Of course: local just means the current scope, wherever you happen to
> be. Inside a function, local is the current function scope. Inside a
> class body, local is the class body scope. At the top level of the
> module, local means the module scope (and locals() returns the same dict
> as globals()).
>
> If Yury means for this "cannot mask existing variables" to only operate
> inside functions, that means that you can mask existing variables if you
> use assignment expressions in class bodies or top-level module code.

I don't have a quote for it, but I was under the impression that this
shielding was indeed function-scope-only.

Actually, now that I think about it, I'm not sure whether Yuri's plan
for assignment expressions even included module scope.

ChrisA

Barry Warsaw

unread,
Apr 24, 2018, 2:41:13 PM4/24/18
to python-dev
On Apr 24, 2018, at 06:55, Antoine Pitrou <soli...@pitrou.net> wrote:
>
> If the ambition is to find a piece of syntax that reads as "binds",
> then we can use a variation on the FLUFL operator: "<->".

FLUFL Override Approved!

-Barry

signature.asc

Anthony Flury via Python-Dev

unread,
Apr 24, 2018, 5:42:10 PM4/24/18
to pytho...@python.org
On 24/04/18 17:11, Yury Selivanov wrote:
> On Tue, Apr 24, 2018 at 12:03 PM, Ethan Furman <et...@stoneleaf.us> wrote:
> [..]
>> But I do write this:
>>
>> def wrapper(func, some_value):
>> value_I_want = process(some_value)
>> def wrapped(*args, **kwds):
>> if value_I_want == 42:
>> ...
> But this pattern is more rare than comparing local variables. That's
> the point I'm trying to use. Besides, to make it an assignment
> expression under my proposal you would need to use parens. Which makes
> it even less likely that you confuse '=' and '=='.

Just because you wrap a set of character in parens doesn't mean that you
wont potentially mistype what you should type inside the parens. The
failure mode of in C :

    if (a = 3)
        do_something_with_a(a);

Is Incredibly common even with very experienced developers - so much so
that most linters flag it as a likely error, and I think gcc has an
option to flag it as a warning - even though it is valid and very
occasionally it is useful.

Also many developers who come to Python from languages such as C will
still place parens around conditionals - this means that a typo which
will cause a Syntax Error in current versions, but would cause a
potentially subtle bug under your implementation (unless you maintain
the rule that you can't rebind currently bound names - which renders the
whole idea useless in loops (as already discussed at length).

I also still can't think of a single other Python construct where the
semantics of an operator are explicitly modified by syntaxtic elements
outside the operator. For mathematical operators, the surrounding parens
modifies the grouping of the operators but not the semantics (* means *,
it is just the operands which potentially change).

You could argue that your proposal overloads the semantics of the parens
(similar to how braces are overloaded to implement dictionaries and set
literals), but I don't think that overloading the semantics of parens is
good idea.

If Python is going to do assignment expressions we shouldn't overload
parens in my opinion - we should have a separate operator - doing this
avoids needing to exclude rebinding, and makes such expressions
considerably more useful.

--
Anthony Flury
email : *Anthon...@btinternet.com*
Twitter : *@TonyFlury <https://twitter.com/TonyFlury/>*

Chris Angelico

unread,
Apr 24, 2018, 6:02:59 PM4/24/18
to python-dev
On Wed, Apr 25, 2018 at 7:29 AM, Anthony Flury via Python-Dev
<pytho...@python.org> wrote:
> On 24/04/18 17:11, Yury Selivanov wrote:
>>
>> On Tue, Apr 24, 2018 at 12:03 PM, Ethan Furman <et...@stoneleaf.us> wrote:
>> [..]
>>>
>>> But I do write this:
>>>
>>> def wrapper(func, some_value):
>>> value_I_want = process(some_value)
>>> def wrapped(*args, **kwds):
>>> if value_I_want == 42:
>>> ...
>>
>> But this pattern is more rare than comparing local variables. That's
>> the point I'm trying to use. Besides, to make it an assignment
>> expression under my proposal you would need to use parens. Which makes
>> it even less likely that you confuse '=' and '=='.
>
>
> Just because you wrap a set of character in parens doesn't mean that you
> wont potentially mistype what you should type inside the parens. The failure
> mode of in C :
>
> if (a = 3)
> do_something_with_a(a);
>
> Is Incredibly common even with very experienced developers - so much so that
> most linters flag it as a likely error, and I think gcc has an option to
> flag it as a warning - even though it is valid and very occasionally it is
> useful.

Technically what you have there trips *two* dodgy conditions, and
could produce either warning or both:

1) Potential assignment where you meant comparison
2) Condition is always true.

The combination makes it extremely likely that this wasn't intended.
It's more dangerous, though, when the RHS is a function call...

> Also many developers who come to Python from languages such as C will still
> place parens around conditionals - this means that a typo which will cause a
> Syntax Error in current versions, but would cause a potentially subtle bug
> under your implementation (unless you maintain the rule that you can't
> rebind currently bound names - which renders the whole idea useless in loops
> (as already discussed at length).

Yuri, look how many of the python-dev readers have completely
misinterpreted this "can't rebind" rule. I think that's a fairly clear
indication that the rule is not going to be well understood by anyone
who isn't extremely familiar with the way the language is parsed. I
hesitate to say outright that it is a *bad rule*, but that's the lines
I'm thinking along.

ChrisA

Antoine Pitrou

unread,
Apr 24, 2018, 6:18:01 PM4/24/18
to pytho...@python.org
On Tue, 24 Apr 2018 09:38:33 -0400
Yury Selivanov <yseliv...@gmail.com> wrote:
> I propose to use the following syntax for assignment expressions:
>
> ( NAME = expr )
>
> I know that it was proposed before and this idea was rejected, because
> accidentally using '=' in place of '==' is a pain point in
> C/C++/JavaScript.

To solve this issue, I would suggest another syntax:

var NAME = expr

Strong points:
- the "var" keyword makes it clear that it's not a mistyped equality
("var NAME == expr" would be a syntax error)
- the "var" keyword can stand out thanks to syntax highlighting
- the "=" which traditionally spells assignement is there as well

Weak points:
- we need a deprecation cycle before "var" can be used as a keyword

(alternative keyword choices against "var": "using", "let", "bind"...)

Regards

Antoine.

Guido van Rossum

unread,
Apr 24, 2018, 6:56:37 PM4/24/18
to Antoine Pitrou, Python-Dev
We should really take this back to python-ideas at this point.

Steven D'Aprano

unread,
Apr 24, 2018, 7:37:14 PM4/24/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 10:29:11PM +0100, Anthony Flury via Python-Dev wrote:

> If Python is going to do assignment expressions we shouldn't overload
> parens in my opinion - we should have a separate operator - doing this
> avoids needing to exclude rebinding, and makes such expressions
> considerably more useful.

Exactly!

Yury's suggestion to prohibit rebinding existing names is throwing away
the baby with the bathwater. Using assignment-expression to over-write
an existing variable is only a error when it is a mistake, done by
accident.

To satisfy the compiler, we have to invent new and unique names for what
is conceptually the same variable being reused. Instead of simplifying
your code, it will make it more complex.

Despite his statement that there's only one assignment, there are
actually two: assignment that allows rebinding, and assignment that
sometimes doesn't. The semantic differences between Yury's = and = are
actually *greater* than the differences between = and :=


--
Steve

Cameron Simpson

unread,
Apr 24, 2018, 8:03:47 PM4/24/18
to Ethan Furman, pytho...@python.org
On 24Apr2018 08:51, Ethan Furman <et...@stoneleaf.us> wrote:
>>When I compare to variables from outer scopes they *usually* are on
>>the *right* side of '=='.
>
>You mean something like
>
> if 2 == x:
>
>? I never write code like that, and I haven't seen it, either.

Just to this, I also never write code like that but I've certainly seen it
advocated.

I think the rationale was that it places the comparison value foremost in one's
mind, versus the name being tested. I'm not persuaded, but it is another
subjective situation.

Cheers,
Cameron Simpson <c...@cskk.id.au>

MRAB

unread,
Apr 24, 2018, 9:02:47 PM4/24/18
to pytho...@python.org
On 2018-04-24 23:32, Cameron Simpson wrote:
> On 24Apr2018 08:51, Ethan Furman <et...@stoneleaf.us> wrote:
>>>When I compare to variables from outer scopes they *usually* are on
>>>the *right* side of '=='.
>>
>>You mean something like
>>
>> if 2 == x:
>>
>>? I never write code like that, and I haven't seen it, either.
>
> Just to this, I also never write code like that but I've certainly seen it
> advocated.
>
> I think the rationale was that it places the comparison value foremost in one's
> mind, versus the name being tested. I'm not persuaded, but it is another
> subjective situation.
>
It's sometimes advocated in C/C++ code to help catch the inadvertent use
of = instead of ==, but that's not a problem in Python.

Steven D'Aprano

unread,
Apr 25, 2018, 1:23:22 AM4/25/18
to pytho...@python.org
On Tue, Apr 24, 2018 at 03:54:30PM -0700, Guido van Rossum wrote:

> We should really take this back to python-ideas at this point.

Please no :-(

Variants of "let" etc were discussed earlier and didn't seem to get much
interest. Although I don't think "var" specifically was suggested
before, "let" certainly has been:

https://mail.python.org/pipermail/python-ideas/2018-February/049031.html

In the same thread, I also independently suggested "let":

https://mail.python.org/pipermail/python-ideas/2018-February/048981.html

This suggestion didn't seem to get much in the way of support, so much
so that in the hundreds of emails for PEP 572 the idea seems to have
been completely forgotten until now. Perhaps it ought to be added to the
PEP in the rejected syntax section.

I don't think there's anything to gain by rehashing this again: whether
we choose "let", "var" or "bind", there's no real difference. It's still
a new keyword, and it still looks like little else in Python today:

while (var spam = expression) and spam in values:
...

It's not really awful, but nor is it clearly better than := and the fact
that it will require a keyword is a major point against it.

I'm speaking for myself, of course, not Chris, but while consensus is of
course desirable I think there comes a time where the PEP author is
entitled to say "I've heard enough arguments, and I'm not convinced by
them, this is my proposal and here it stays". If it were my PEP, at this
point I would say that if anyone wants to champion an alternative syntax
they can write their own PEP :-)

I think this idea is going to be too divisive to expect a consensus to
emerge, rather like the ternary if operator. I truly believe we've
covered just about everything that needs covering, over many hundreds of
emails in multiple threads, and unless somebody puts their hand up to
write a competing PEP (or at least says something new beyond what we've
already seen many times before) I think it is just about time for a BDFL
decision between:

1. Reject the PEP. Not going to happen.

2. Leave the PEP pending. Maybe some time in the future.

3. Accept the PEP using the `name := expression` syntax.

My preference would be for 3, even though := isn't my first choice for
syntax, it's still a good choice. I think there are sufficient use-cases
to justify this feature, including some long standing annoyances such as
the dance we have to play with regexes, which only gets more annoying as
you add more alternatives and cascading indentation.

mo = re.match(pattern1, string)
if mo:
process(mo)
else:
mo = re.match(pattern2, string)
if mo:
handle(mo)
else:
mo = ... # you get the picture


(I've changed my mind from earlier when I said this didn't count as a
separate use-case from those already given. I think that cascading
regexes is a big enough pain that it should be explicitly listed in the
PEP as a motivation.)


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

Greg Ewing

unread,
Apr 25, 2018, 1:53:57 AM4/25/18
to Python-Dev
Nick Coghlan wrote:
> I'd be +0 on an "is=" spelling

But "is=' looks like some kind of comparison
operator. This seems even more conusing to me.

--
Greg

Steve Holden

unread,
Apr 25, 2018, 5:29:34 AM4/25/18
to Steven D'Aprano, Python-Dev@Python. Org
On Wed, Apr 25, 2018 at 6:16 AM, Steven D'Aprano <st...@pearwood.info> wrote:
On Tue, Apr 24, 2018 at 03:54:30PM -0700, Guido van Rossum wrote:

> We should really take this back to python-ideas at this point.

Please no :-(

​+1

Guido van Rossum

unread,
Apr 25, 2018, 11:48:25 AM4/25/18
to Steve Holden, Python-Dev@Python. Org
Maybe I should have just said "no". Thanks Steven D'A for saving everyone yet another detour on this proposal.

I'm pretty excited myself about NAME := <expression> and am mostly ignoring the current crop of counter-proposal.
 

Chris Angelico

unread,
Apr 25, 2018, 11:53:06 AM4/25/18
to Python-Dev@Python. Org
Of course, if someone wants to start a python-bad-ideas mailing list,
I'm sure some of these alternatives would be perfect for it...

If anyone hasn't seen the latest iteration of the PEP, I recently
re-posted it. And it's always available here:

https://www.python.org/dev/peps/pep-0572/

ChrisA
Reply all
Reply to author
Forward
0 new messages