6

I cannot produce example in Python which shows Boolean operator precedence rules combined with short circuit evaluation. I can show operator precedence using:

print(1 or 0 and 0)  # Returns 1 because `or` is evaluated 2nd.

But the issue with short circuiting shows up when I change it to this:

def yay(): print('yay'); return True
def nay(): print('nay')
def nope(): print('nope')
print(yay() or nay() and nope())  # Prints "yay\nTrue"

For each of 4 possibilities when expression before or is True it is the only evaluated expression. If operator precedence works this should print "nay\nnope\nyay\nTrue" or "nay\nyay\nTrue", with short circuiting, because and should be evaluated 1st.

What comes to mind from this example is that Python reads boolean expression from left to right and ends it when result is known regardless of operator precedence.

Where is my error or what am I missing? Please give an example where it's visible that and is evaluated 1st and it isn't due to code being interpreted from left to right.

1
  • 4
    Note that operator precedence is not the same as evaluation order. Commented Aug 10, 2018 at 10:08

5 Answers 5

10

You are confusing operator precedence and evaluation order.

The expression r = x or y and z is not evaluated as tmp = y and z; r = x or tmp, but just as r = x or (y and z). This expression is evaluated from left to right, and if the result of the or is already decided, then (y and z) will not be evaluated at all.

Note that it would be different if or and and were functions; in this case, the parameters of the functions would be evaluated before the function itself is called. Hence, operator.or_(yay(), operator.and_(nay(), nope())) prints yay, nay and nope i.e. it prints all three, but still in order from left to right.

You can generalize this to other operators, too. The following two expressions will yield different results due to the different operator precedence (both implicit and explicit by using (...)), but the functions are called from left to right both times.

>>> def f(x): print(x); return x
>>> f(1) + f(2) * f(3) / f(4) ** f(5) - f(6)         # 1 2 3 4 5 6 -> -4.99
>>> (f(1) + f(2)) * (((f(3) / f(4)) ** f(5)) - f(6)) # 1 2 3 4 5 6 -> -17.29

As pointed out in comments, while the terms in between operations are evaluated from left to right, the actual operations are evaluated according to their precedence.

class F:
    def __init__(self,x): self.x = x
    def __add__(self, other): print(f"add({self},{other})"); return F(self.x+other.x)
    def __mul__(self, other): print(f"mul({self},{other})"); return F(self.x*other.x)
    def __pow__(self, other): print(f"pow({self},{other})"); return F(self.x**other.x)
    def __repr__(self): return str(self.x)
def f(x): print(x); return F(x)

This way, the expression f(1) + f(2) ** f(3) * f(4) is evaluated as 1, 2, 3, pow(2,3), 4, mul(8,4), add(1,32), i.e. terms are evaluated left-to-right (and pushed on a stack) and expressions are evaluated as soon as their parameters are evaluated.

Sign up to request clarification or add additional context in comments.

4 Comments

I know that all arguments must be evaluated before they are passed to function but your example with operator.or_ and operator.and_ makes an interesting counterexample and shows that and and or operators evaluation cannot be replicated with function from operator. So operator precedence is only about implicit parenthesis nesting not about evaluation order right?
In your example the functions are all called in order left to right, but the operator evaluation happens in precedence order: so define a class which implements arithmetic operators and prints them and you'll see the calculations are not left to right. Indeed if you do that you'll even see that f(1) ** f(2) ** f(3) calls the __pow__ method right to left.
@Duncan Yes, operations are evaluated by precedence, I did not mean to deny this. Hope my edit clears it up.
One might argue that right to left evaluation doesn't occur here but it only seems like it because number of expressions is getting smaller when operator is called thus the line gets shorter. You're not intentionally going back, it's like going against escalator direction.
2

The first value returned from yay() is True so python will not even run the rest of the expression. This is for efficiency, as the rest of the expression will not impact the result.

Take the following example where I have changed the order:

def yay(): print('yay'); return True
def nay(): print('nay')
def nope(): print('nope')
print(nay() and nope() or yay())

Output:

nay
yay
True

What is happening here? nay() returns None which is falsy so we already know that it doesn't matter what nope() returns (because it is an and condition and the first part is already False) - so we don't run it. We then run the yay() because it could change the already False value of the expression - which returns True.

2 Comments

This is good example for short cirquiting but it doesn't show difference between operator precedence and evaluation order. That is why I asked for and which isn't evaluated 1st because of the left-to-rigth evaluation order.
Shortcut evaluation is not only for efficiency. It also allows constructions where the second part does not make sense unless the first part has a certain outcome, as in b != 0 and a/b > 1.
2

or has lower precedence than and so that a or b and c is interpreted as a or (b and c).

In addition, the logical operators are evaluated "lazily" so that if a is True, a or b will not cause the evaluation of b.

Comments

1

Actually, your code returns 1 not because or is evaluated 2nd but because 1 is true and no further evalution is necessary. Which makes the behaviour consistent.

Comments

1

Python evaluates expression from left to right and stops as soon as the result is known. For ex, in case of an or operator, if the entity of left side is True then it is for sure that the operator will return true do the expression on right will not be evaluated in this case.

In case of and operator, if the expression on the left side is False, it is sure that the operator should return False. So the expression to the right is not evaluated here.

This is what is happening in your example.

4 Comments

It doesn't evaluate everything left to right: a if cond else b will evaluate cond before either a or b.
I didn't understand the syntax here. Can you explain a bit. I am a bit curious to know about what you want to say.
I'm just saying that while most expressions in Python evaluate left to right there are exceptions. The conditional expression evaluates the condition and then evaluates one of the left or right expressions, so when the condition is true it cannot follow the left-to-right rule. See docs.python.org/3/reference/…
Thanks for letting me know.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.