[Python-Dev] assignment expressions: an alternative proposal

Steven D'Aprano steve at pearwood.info
Tue Apr 24 12:23:46 EDT 2018


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


More information about the Python-Dev mailing list