Search code examples
pythonsyntaxoperator-precedence

Precedence of "in" in Python


This is a bit of a (very basic) language-lawyer kind of question. I understand what the code does, and why, so please no elementary explanations.

In an expression, in has higher precedence than and. So if I write

if n in "seq1" and "something":
    ...

it is interpreted just like

if (n in "seq1") and "something":
    ...

However, the in of a for loop has lower precedence than and (in fact it has to, otherwise the following would be a syntax error). Hence if a Python beginner writes

for n in "seq1" and "something":
    ...

..., it is equivalent to this:

for n in ("seq1" and "something"):
    ...

(which, provided "seq1" is truthy, evaluates to for n in "something").

So, the question: Where is the precedence of the for-loop's in keyword specified/documented? I understand that n in ... is not an expression in this context (it does not have a value), but is part of the for statement's syntax. Still, I'm not sure how/where non-expression precedence is specified.


Solution

  • In the context of a for statement, the in is just part of the grammar that makes up that compound statement, and so it is distinct from the operator in. The Python grammar specification defines a for statement like this:

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

    The point to make is that this particular in will not be interpreted as part of target_list, because a comparison operation (e.g. x in [x]) is not a valid target. Referring to the grammar specification again, target_list and target are defined as follows:

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

    So the grammar ensures that the parser sees the first in token after a target_list as part of the for ... in ... statement, and not as a binary operator. This is why trying to write things very strange like for (x in [x]) in range(5): will raise a syntax error: Python's grammar does not permit comparisons like (x in [x]) to be targets.

    Therefore for a statement such as for n in "seq1" and "something" is unambiguous. The target_list part is the identifier n and the expression_list part is the iterable that "seq1" and "something" evaluates to. As the linked documentation goes on to say, each item from the iterable is assigned to target_list in turn.