Does Python support short-circuiting?
Asked Answered
M

3

460

Does Python support short-circuiting in boolean expressions?

Myra answered 5/4, 2010 at 18:19 Comment(1)
Closely related: Strange use of “and” / “or” operatorAcre
R
441

Yep, both and and or operators short-circuit -- see the docs.

Religiosity answered 5/4, 2010 at 18:20 Comment(0)
K
271

Short-circuiting behavior in operator and, or:

Let's first define a useful function to determine if something is executed or not. A simple function that accepts an argument, prints a message and returns the input, unchanged.

>>> def fun(i):
...     print "executed"
...     return i
... 

One can observe the Python's short-circuiting behavior of and, or operators in the following example:

>>> fun(1)
executed
1
>>> 1 or fun(1)    # due to short-circuiting  "executed" not printed
1
>>> 1 and fun(1)   # fun(1) called and "executed" printed 
executed
1
>>> 0 and fun(1)   # due to short-circuiting  "executed" not printed 
0

Note: The following values are considered by the interpreter to mean false:

        False    None    0    ""    ()    []     {}

Short-circuiting behavior in function: any(), all():

Python's any() and all() functions also support short-circuiting. As shown in the docs; they evaluate each element of a sequence in-order, until finding a result that allows an early exit in the evaluation. Consider examples below to understand both.

The function any() checks if any element is True. It stops executing as soon as a True is encountered and returns True.

>>> any(fun(i) for i in [1, 2, 3, 4])   # bool(1) = True
executed
True
>>> any(fun(i) for i in [0, 2, 3, 4])   
executed                               # bool(0) = False
executed                               # bool(2) = True
True
>>> any(fun(i) for i in [0, 0, 3, 4])
executed
executed
executed
True

The function all() checks all elements are True and stops executing as soon as a False is encountered:

>>> all(fun(i) for i in [0, 0, 3, 4])
executed
False
>>> all(fun(i) for i in [1, 0, 3, 4])
executed
executed
False

Short-circuiting behavior in Chained Comparison:

Additionally, in Python

Comparisons can be chained arbitrarily; for example, x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).

>>> 5 > 6 > fun(3)    # same as:  5 > 6 and 6 > fun(3)
False                 # 5 > 6 is False so fun() not called and "executed" NOT printed
>>> 5 < 6 > fun(3)    # 5 < 6 is True 
executed              # fun(3) called and "executed" printed
True
>>> 4 <= 6 > fun(7)   # 4 <= 6 is True  
executed              # fun(3) called and "executed" printed
False
>>> 5 < fun(6) < 3    # only prints "executed" once
executed
False
>>> 5 < fun(6) and fun(6) < 3 # prints "executed" twice, because the second part executes it again
executed
executed
False

Edit:
One more interesting point to note :- Logical and, or operators in Python returns an operand's value instead of a Boolean (True or False). For example:

Operation x and y gives the result if x is false, then x, else y

Unlike in other languages e.g. &&, || operators in C that return either 0 or 1.

Examples:

>>> 3 and 5    # Second operand evaluated and returned 
5                   
>>> 3  and ()
()
>>> () and 5   # Second operand NOT evaluated as first operand () is  false
()             # so first operand returned 

Similarly or operator return left most value for which bool(value) == True else right most false value (according to short-circuiting behavior), examples:

>>> 2 or 5    # left most operand bool(2) == True
2    
>>> 0 or 5    # bool(0) == False and bool(5) == True
5
>>> 0 or ()
()

So, how is this useful? One example is given in Practical Python By Magnus Lie Hetland:
Let’s say a user is supposed to enter his or her name, but may opt to enter nothing, in which case you want to use the default value '<Unknown>'. You could use an if statement, but you could also state things very succinctly:

In [171]: name = raw_input('Enter Name: ') or '<Unknown>'
Enter Name: 

In [172]: name
Out[172]: '<Unknown>'

In other words, if the return value from raw_input is true (not an empty string), it is assigned to name (nothing changes); otherwise, the default '<Unknown>' is assigned to name.

Kenakenaf answered 15/2, 2013 at 10:34 Comment(7)
Minor quibble: The explicit list of falsy values is slightly misleading. Any type can have one or more falsy values. By convention, all numeric types with value 0 are falsy (so it's not just 0, it's 0.0, 0j, decimal.Decimal(0), fractions.Fraction(0), etc.), as are all collections with length 0 (so on top of what you listed, b'' [Py3], u'' [Py2] and set()/frozenset() are all built-ins that evaluate as falsy), but user-defined/third-party types can define their own (with __bool__ [Py3]/__nonzero__ [Py2] directly, or indirectly by defining __len__).Winepress
@Winepress here your comment will complete my answer. thanks for adding this note.Kenakenaf
Also, python double-evaluates short circuited conditionals, if later used as booleans... unless they are in an if statement, which is priviliged: gist.github.com/earonesty/08e9cbe083a5e0583feb8a34cc538010Angio
@GrijeshChauhan does python support long circuit?Bankbook
@KeerthanaPrabhakaran :( sorry I do not know about that. If you post a new question then please share with me.Kenakenaf
There is a caveat when using all, in that it won't behave exactly like chaining booleans using and expressions. It will evaluate everything in the list. This can cause confusion when relying on expressions that must short circuit to avoid raising an exception. For example: If n = len(nums) and i >= n, then i < n and nums[i] > 0 would not raise an exception, yet all([i < n, nums[i] > 0]) would raise an IndexError.Timon
@Timon your expressions are not equal, in all() you are forcing nums[i] > 0] to evaluate in a listKenakenaf
H
63

Yes. Try the following in your python interpreter:

and

>>>False and 3/0
False
>>>True and 3/0
ZeroDivisionError: integer division or modulo by zero

or

>>>True or 3/0
True
>>>False or 3/0
ZeroDivisionError: integer division or modulo by zero
Hypersthene answered 26/7, 2013 at 18:52 Comment(1)
How can one do something like myVar == 5 and continue? It seems to not be valid.Jespersen

© 2022 - 2024 — McMap. All rights reserved.