4

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?

martineau
  • 119,623
  • 25
  • 170
  • 301
Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144
  • Is `(True and obj).e = "hello"` valid syntax? – Charles Duffy Sep 16 '16 at 20:27
  • ...answering my own question: No, it's not. – Charles Duffy Sep 16 '16 at 20:27
  • what about `for k,v in dictionary.items()` ? – Jean-François Fabre Sep 16 '16 at 20:28
  • 3
    @CharlesDuffy `(True and obj).e = "hello"` _is_ valid syntax – khelwood Sep 16 '16 at 20:30
  • What about `for x in sum[1]: pass` – MooingRawr Sep 16 '16 at 20:30
  • @khelwood, ...what am I doing wrong in the test given in my answer? – Charles Duffy Sep 16 '16 at 20:31
  • @CharlesDuffy In your answer you have `(True and obj.e)`, not `(True and obj).e` – khelwood Sep 16 '16 at 20:31
  • @khelwood, ahh, thank you. – Charles Duffy Sep 16 '16 at 20:32
  • Agree, this is embarrassing. Just after you hear how Python is elegant and easy and so on, you see THIS. Then you learn that random fields can be added to a function.... Then you're told this is not oversight at all, this is by design. They should be using other vocabulary where elegant and safe are defined in some pervert weird way – ddbug Sep 16 '16 at 21:17
  • @ddbug 99% of Python is great. This oddity, some objects not being immutable when they should be, and `(True, False) == (1, 0)` are really the only things that bother me. – Uyghur Lives Matter Sep 16 '16 at 21:56
  • @ddbug, eh? Functions are objects, objects can have arbitrary fields associated. I'd find it more of a violation of Principal of Least Surprise if one *couldn't* assign (meta)data to functions (like one can to any other object type that isn't explicitly given a limited slot list, a `__setattr__()` function, or similar) in previously-unused fields. – Charles Duffy Sep 16 '16 at 22:09
  • Possible duplicate of [Why are arbitrary target expressions allowed in for-loops?](https://stackoverflow.com/questions/44317993/why-are-arbitrary-target-expressions-allowed-in-for-loops) – jtbandes Jul 25 '17 at 17:20
  • 1
    @jtbandes Shouldn't your question be a duplicate of mine? They're essentially the same, and mine predates yours by 3/4 of a year. – Uyghur Lives Matter Jul 25 '17 at 20:55
  • Doesn't matter too much I suppose. https://meta.stackoverflow.com/questions/251938/should-i-flag-a-question-as-duplicate-if-it-has-received-better-answers – jtbandes Jul 26 '17 at 00:25

2 Answers2

6

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.

Community
  • 1
  • 1
wim
  • 338,267
  • 99
  • 616
  • 750
1

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.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384