Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 948
Uyghur Lives Matter Avatar asked Mar 12 '23 03:03

Uyghur Lives Matter


2 Answers

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.

like image 74
wim Avatar answered Mar 19 '23 11:03

wim


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.

like image 44
BrenBarn Avatar answered Mar 19 '23 12:03

BrenBarn