Search code examples
pythonpython-3.xoperatorslogical-operatorsoperator-precedence

Confused about Operator Precedence in Python


I was playing around with logical expressions in the python interpreter, and I can't seem to figure out what execution procedure python is really using under the hood. I've seen this table (http://www.mathcs.emory.edu/~valerie/courses/fall10/155/resources/op_precedence.html) as describing the operator precedence that python uses.

1)

print("this") or True and 1/0 and print("tester")

when I type that into the python interpreter I get the output of "this" and then zero division error. However, the site I referenced mention that function calls are the second highest precedence, so shouldn't both function calls of print be executed first? I know there's short circuit evaluation, but doesn't that kick in only once you get to the precedence level of the ands, nots, and ors?

2)

True>False or print("hello")

even this outputs only True on the python interpreter. why doesn't it do the function call of print first?

3)

5 is 5 or 1/0

This outputs True. But shouldn't division have higher precedence than "is" and shouldn't this expression return a ZeroDivsionError?

Can someone explain what I'm missing and how to tell in what order python will execute a logical expression?


Solution

  • Can someone explain what I'm missing and how to tell in what order python will execute a logical expression?

    Precedence affects which way "shared" operands will be grouped when parsing to a tree. Past that, the specific evaluation model of each sub-expression takes over.

    print("this") or True and 1/0 and print("tester")

    You get a tree which looks like this (you can get the more verbose but exact version using the ast module, and astpretty to get something readable):

        or
            print("this")
            and
                 True
                 and
                     1/0
                     print("tester")
    

    Then evaluation takes over (after a compilation to bytecode but that doesn't change the order of operation):

    1. the outer or is evaluated
      1. it evaluates the first print, which returns a falsy value, therefore
      2. it evaluates the first and
        1. it evaluates True which is truthy
        2. therefore it evaluates the second and
          1. which evaluates 1/0 which blows up

    True>False or print("hello")

    This parses to

    or
       >
          True
          False
       print("hello")
    
    1. or evaluates its first operand

      1. (> True False) evaluates to True

      or is a short-circuiting operator, it stops as soon as it finds a truthy value and returns that, so it never gets to the print

    5 is 5 or 1/0

    This parses to

    or
        is
            5
            5
        /
            1
            0
    
    1. or is evaluated

      1. is is evaluated, and returns True

      as above, or is a short-circuiting operator and returns the first truthy value, so it returns immediately.

    I left out some bits e.g. technically / evaluates both operands then applies its operation, function calls evaluate all their parameters then perform the call itself.

    and and or stand out because they perform logic after the evaluation of each operand, not after having evaluated all of them (they're lazy / short-circuiting): and returns the first falsy result it gets, or returns the first truthy result it gets, potentially evaluating only the first of their operands.