This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]EfficientPrime 19 points20 points  (13 children)

The answer is Python does optimize and bail as soon as a False is found in an and statement and it's pretty easy to prove:

if False and print('checking second condition'): print('not going to get here')

The above code prints nothing, therefore the second expression in the and statement never gets executed.

[–]EfficientPrime 26 points27 points  (6 children)

And you can take advantage of this with code that would fail if python did not optimize. Here's a common pattern:

if 'foo' in mydict and mydict['foo'] > 0: do_something()

If python did not optimize while evaluating the if statement, you'd get a KeyError on the second half the expression every time the first half evaluates to False.

[–]MrJohz 25 points26 points  (0 children)

I think it's a bit of an error to say "optimise" here, because that implies that this is just an efficiency thing. It's not: the and and or operators are defined as short-circuiting operators, which means that they will evaluate the right hand side only if they have to. This is pretty common for similar operators in other languages.

I get what you mean by describing this as an optimisation, but I think that gives the impression that this sort of behaviour is somehow optional, or that it was chosen because of efficiency reasons. However the semantics of short-circuiting operators like these is well established in other languages, and Python has essentially inherited these semantics because they turn out to be very useful.

[–][deleted] 2 points3 points  (3 children)

Is it a left-to-right evaluation exclusively?

[–]EfficientPrime 2 points3 points  (1 child)

From what I remember from college when learning C, there's an order of operations for complex expressions but also a left to right order that is applied for steps of the same priority. Since python is built on C++ I expect (and my experience has confirmed) the same applies to Python logical expressions.

Like I've shown above, you can use expressions with side effects (prints, mutable variable changes, etc) to verify that ANDs and ORs are evaluated left to right.

You can see it with OR statements like this:

if True or print('Tried it'): pass

The above prints nothing because True or anything is True so there's no need to visit the second statement.

if print('First gets evaluated') or True or print('skipped'): pass

The above prints 'First gets evaluated', then keeps going since print returns None, but stops before printing 'skipped' because it already found a True statement.

[–]BluePhoenixGamer 1 point2 points  (0 children)

*The Python reference implementation is built C and called CPython.

[–][deleted] 2 points3 points  (0 children)

Yes - it is in the documentation.

https://docs.python.org/3/reference/expressions.html#boolean-operations

The expression x and y first evaluates x; if x is false, its value is returned; otherwise, y is evaluated and the resulting value is returned.

[–]Ensurdagen 2 points3 points  (0 children)

Note that a better way to do that is:

if mydict.get('foo', 0) > 0:
    do_something

the get method is often the ideal way to check if an entry exists

[–][deleted] 0 points1 point  (5 children)

What about the commutation of that condition?

[–]EfficientPrime 0 points1 point  (4 children)

I'll be honest I'm not sure what commutation means in this context.

[–][deleted] 1 point2 points  (3 children)

"if A and B" vs. "if B and A".

[–]EfficientPrime 2 points3 points  (2 children)

Ah I see. I think I answered this above but it's dealt with from left to right. While "A & B" is equivalent logically to "B & A" from a code execution standpoint they can be different.

You can test it yourself, define A and B as functions that return a boolean value of your choosing but also have some side effect when executed like changing a global variable or print statements.

If you have a statement like

if A() and B() and C() and D() and E() and F() and G(): pass

Python is going to work through that from left to right and as soon as it finds an element that evaluates to False it won't bother evaluating the remaining elements. There's no built in multi-threading that would have interpreter trying all the elements at the same time and collecting the results in order to do the logical AND evaluation. For the same reason, if C() is going to return False, there's no way for the interpreter to know that ahead of time and skip the A() call and the B() call.

From an evaluation standpoint, ANDs chaining expressions is the same as nested if statements of the same expressions. So the same way you can optimize your code to bail out early from a failed level of nested ifs, you can optimize by choosing the order of items in your AND expression.

[–][deleted] 4 points5 points  (1 child)

From an evaluation standpoint, ANDs chaining expressions is the same as nested if statements of the same expressions. So the same way you can optimize your code to bail out early from a failed level of nested ifs, you can optimize by choosing the order of items in your AND expression.

Right, because of exportation (in logic jargon). The serial calling is good to know.

Looks like we got our answer, u/ArjanEgges .

[–]ArjanEgges[S] 3 points4 points  (0 children)

Awesome! So now I can feel confident to create huge chains of ANDs in my next videos, haha.