[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