Home > Software design >  Why does 'not(True) * True' not give me an invalid syntax error, just like 'True * no
Why does 'not(True) * True' not give me an invalid syntax error, just like 'True * no

Time:08-11

Is this a consequence of the hierarchical order of operators in Python?

not(True)* True
#False
True* not(True)
#SyntaxError: invalid syntax

CodePudding user response:

Is this a consequence of the hierarchical order of operators in Python?

Yes (although the usual term is operator precedence). Summing up and simplifying:

  1. not isn't a function; it's an operator. Therefore, we don't need to write parentheses for not (True), and in fact they don't do anything here. All that happens is that the parentheses are treated as ordinary grouping parentheses - (True) is evaluated before anything else, becoming True. So, let's consider the examples without the parentheses.

  2. not True * True means not (True * True). It does not mean (not True) * True, due to operator precedence. This is by design:

    >>> not 1 * 0
    True
    >>> not (1 * 0)
    True
    >>> (not 1) * 0
    0
    

    It would, the developers figured, be unexpected to write something like not 1 * 0 and get an integer result, and unexpected to write not in front of a mathematical operation and have the not only apply to the first thing in that expression.

  3. Because of that same operator precedence, True * not True is a syntax error. Python parses the not by itself as the right-hand side of the *, because it hasn't had a chance to apply not True yet. True * not is obviously nonsense. Or, another way of looking at it: "not followed by an expression" isn't in the list of "things that can be an operand for *".

    This is perhaps surprising because the other commonly used unary operator, - (i.e., unary negation), doesn't have this issue. But that's because the precedence is the other way around: unary negation is processed before multiplication, not after.

    The same is true for and and or combinations:

    >>> 3 * 5 and 1 # 3 * 5 is evaluated first
    1
    >>> 3 * (5 and 1)
    3
    >>> 3 or 1 * 5 # 1 * 5 is evaluated first, even though it comes later
    3
    >>> (3 or 1) * 5
    15
    

CodePudding user response:

Anthony Sotille's answer says Python is trying to parse * not as an "is not" or "not in" operator, but there's nothing in the parser or grammar that would try to do that. This is really a matter of precedence, and how precedence is implemented.

* has a higher precedence than not, and the way that works in the Python grammar is that there's a hierarchy of expression types, structured so that higher-precedence operators can be the "root" operator of an argument to a lower-precedence operator, but not the other way around.

For example, the grammar rule for multiplicative expressions is

term[expr_ty]:
    | a=term '*' b=factor { _PyAST_BinOp(a, Mult, b, EXTRA) }
    | a=term '/' b=factor { _PyAST_BinOp(a, Div, b, EXTRA) }
    | a=term '//' b=factor { _PyAST_BinOp(a, FloorDiv, b, EXTRA) }
    | a=term '%' b=factor { _PyAST_BinOp(a, Mod, b, EXTRA) }
    | a=term '@' b=factor { CHECK_VERSION(expr_ty, 5, "The '@' operator is", _PyAST_BinOp(a, MatMult, b, EXTRA)) }
    | factor

term is the grammar rule for multiplicative expressions. The first 5 options in this rule consist of a multiplicative-precedence operator in the middle, another term on the left of the operator, and a factor on the right, where factor is the rule for the next higher-precedence operator class. The 6th option is just a factor.

Structuring the grammar like this makes sure the parsed syntax tree always matches the structure given by operator precedence, but it also means that lower-precedence operators cannot be the "root" operator of an argument to a higher-precedence operator, even when the expression would seem unambiguous. There's just no grammar rule that would allow a not expression as an argument to a * expression.

(The grammar rules for most expressions follow the above structure, but there are exceptions. For example, the grammar rules for parentheses don't follow the "no lower-precedence operators within higher-precedence expressions" structure, which is why you can write things like 3 * (4 5). Exponentiation is another exception - ** binds tighter than a unary /-/~ on the left, but not on the right, so the rules for ** and unary /-/~ don't follow a clear precedence hierarchy.)

  • Related