Why is logical not after an addition syntactically invalid?

Viewed 67

Today, I noticed that not True + 5 is syntactically valid but 5 + not True isn't. The syntax error points to the first character of not, as if the operator couldn't be used here. However, 5 + (not True) works, which leads me to believe it is a parser issue.

Are there any reasons for this error?

  • Is it a design decision taken by the language?
  • Is it is a parser error, or a behaviour not defined by the language's rules?

I couldn't find any resources concerning this case.

2 Answers

It's an operator precedence issue; see e.g. this table in the documentation. + binds more tightly than not, so this:

not True + 5

Is equivalent to:

not (True + 5)

And True evaluates as 1 in this case (so you get True + 5 == 6, and not 6 == False). On the other hand:

5 + not True

Is:

(5 + not) True

Which doesn't make any sense. You would need to explicitly write:

5 + (not True)

(Which would equal 5, since not True is False and False evaluates to 0.)

We need to look at the Python grammar to see why not True + 5 is syntactically (though not semantically) valid while 5 + not True is a parse error. Here are the most relevant portions:

Relevant grammar rules
inversion:
    | 'not' inversion 
    | comparison

comparison:
    | bitwise_or
bitwise_or:
    | bitwise_xor
bitwise_xor:
    | bitwise_and
bitwise_and:
    | shift_expr
shift_expr:
    | sum

sum:
    | sum '+' term 
    | term

The order of these rules matches precedence of the various operators involved, from lowest precedence to highest. 'not' inversion is listed before sum '+' term because the not operator has lower precedence than +.

not <expr> is parsed as an inversion. Reading top-down following the chain of derivations, you can see that an inversion could be a sum. This means that not True + 5 can be parsed as the not operator followed by an operand of True + 5.

Parse tree for not True + 5
inversion
│       │
'not'   inversion
        │
        comparison
        │
        bitwise_or
        │
        bitwise_xor
        │
        bitwise_and
        │
        shift_expr
        │
        sum
        │
        sum '+' term
        │       │
        term    '5'
        │
        'True'

On the other hand if you have 5 + not True there is no way to construct a parse tree. If you tried, 5 + not True would be parsed as sum '+' term. The left side is fine: 5 can be parsed as a sum. There's no valid parse for the right side, though: not True is not a term.

Failed parse tree for 5 + not True
sum ─┬────┐
│    │    │
sum  '+'  term
│         │
term      <error: no derivation for 'not' 'True'>
│
'5'

NB: The only way to work back "up" the grammar is to add parentheses. not True is not a term, but (not True) with parentheses is. 5 + (not True) is syntactically valid. Of course, it's still semantically invalid. But hey, if you don't care about semantics the parser won't stop you.

Related