cpburnz cpburnz - 1 year ago 38
Python Question

Are for-loop name list expressions legal?

In CPython 2.7.10 and 3.4.3, and PyPy 2.6.0 (Python 2.7.9), it is apparently legal to use expressions (or some subset of them) for the name list in a for-loop. Here's a typical for-loop:

>>> for a in [1]: pass
>>> a

But you can also use attributes from objects:

>>> class Obj(object): pass
>>> obj = Obj()
>>> for obj.b in [1]: pass
>>> obj.b

And you can even use attributes from expressions:

>>> for Obj().c in [1]: pass

But not all expressions appear to work:

>>> for (True and obj.d) in [1]: pass
File "<stdin>", line 1
SyntaxError: can't assign to operator

But they do so long as the attribute is on the outside?

>>> for (True and obj).e in [1]: pass
>>> obj.e

Or something is assignable?

>>> for {}['f'] in [1]: pass

I'm surprised any of these are legal syntax in Python. I expected only names to be allowed. Are these even supposed to work? Is this an oversight? Is this an implementation detail of CPython that PyPy happens to also implement?

wim wim
Answer Source

Are these even supposed to work?


Is this an oversight?


Is this an implementation detail of CPython that PyPy happens to also implement?


If you can assign to it, you can use it as the free variable in the for loop.

For questions like this, it's worth going straight to the grammar:

for_stmt ::=  "for" target_list "in" expression_list ":" suite
              ["else" ":" suite]

A target_list is just a bunch of target:

target_list     ::=  target ("," target)* [","]
target          ::=  identifier
                     | "(" target_list ")"
                     | "[" [target_list] "]"
                     | attributeref
                     | subscription
                     | slicing
                     | "*" target

If you look at that closely, you'll see that none of the working examples you've given is a counter-example. Mind you, bugs in the parser are not unheard of (even I found one once), so if you find a legitimate syntax anomaly then by means submit a ticket - these tend to get fixed quickly.

The most interesting pair you gave is (True and obj.d) and (True and obj).d, which seem to be the same logically but are parsed differently:

>>> ast.dump(ast.parse('(True and obj.d)'), annotate_fields=False)
"Module([Expr(BoolOp(And(), [Name('True', Load()), Attribute(Name('obj', Load()), 'd', Load())]))])"
>>> ast.dump(ast.parse('(True and obj).d'), annotate_fields=False)
"Module([Expr(Attribute(BoolOp(And(), [Name('True', Load()), Name('obj', Load())]), 'd', Load()))])"

Note: (True and obj).d is an attributeref in the grammar.