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
1
But you can also use attributes from objects:
>>> class Obj(object): pass
...
>>> obj = Obj()
>>> for obj.b in [1]: pass
...
>>> obj.b
1
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
1
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?
Are these even supposed to work?
Yes
Is this an oversight?
No
Is this an implementation detail of CPython that PyPy happens to also implement?
No
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.
As documented, the "variable" in the for
syntax can be any target_list
, which, as also documented means anything that can be on the left-hand side of an assignment statement. You obviously can't use something like (True and obj.d)
, because there's no way to assign a value to that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With