import random
def flip_coin():
if random.randint(0, 1):
return "Heads"
else:
return "Tails"
def flip_is_heads():
return flip_coin() == "Heads"Item 23: Pass Iterators to any and all for Efficient Short-Circuiting Logic
Notes
- Imagine try to analyse a coin-flip
- Denote heads as
True, tails asFalse
- Denote heads as
- Want to flip a coin a fixed number of times, and see if every result is heads
- Can perform with a comprehension and an
inquery
import random
def flip_coin():
if random.randint(0, 1):
return "Heads"
else:
return "Tails"
def flip_is_heads():
return flip_coin() == "Heads"
flips = [flip_is_heads() for _ in range(20)]
all_heads = False not in flips
print(all_heads)False
- The above approach performs all twenty coin flips even once a tails has been seen
- If coin flips were instead a more expensive operation this would represent a lot of wasted computation
- We could write a terminating
forloop
import random
def flip_coin():
if random.randint(0, 1):
return "Heads"
else:
return "Tails"
def flip_is_heads():
return flip_coin() == "Heads"
all_heads = True
for _ in range(20):
if not flip_is_heads():
all_heads = False
break
print(all_heads)False
- Code is now much longer and less clear
- We can use the
allbuilt-in to combine the short-circuiting behaviour with a succinct expression allsteps through an iterator, checks for truthy values- Stops processing if not
- Returns
Trueif it reaches the end of the iterator, elseFalse
- This is different to
andwhich returns the value that determines truthyness
print("All truthy")
print(all([1, 2, 3]))
print(1 and 2 and 3)
print("One falsey")
print(all([1, 0, 3]))
print(1 and 0 and 3)All truthy
True
3
One falsey
False
0
- Rewriting our
all_headscalculation
import random
def flip_coin():
if random.randint(0, 1):
return "Heads"
else:
return "Tails"
def flip_is_heads():
return flip_coin() == "Heads"
all_heads = all(flip_is_heads() for _ in range(20))
print(all_heads)False
- Stops doing coin flips as soon as a
Falsevalue is met - If we pass a list comprehension the list is generated first
- Which defeats the whole point of using
all
- Which defeats the whole point of using
- You can use a generator expression instead
- i.e. something that
yield’s - So it’s only called as required
- i.e. something that
import random
def flip_coin():
if random.randint(0, 1):
return "Heads"
else:
return "Tails"
def flip_is_heads():
return flip_coin() == "Heads"
all_heads = all([flip_is_heads() for _ in range(20)]) # list comprehension - Wrong
print(all_heads)
def repeated_is_heads(count):
for _ in range(count):
yield flip_is_heads() # Generator
all_heads = all(repeated_is_heads(20)) # generator expression is good
print(all_heads)False
False
- When a
Falseis found,allstops calling the iterator and the result is returned- No references exist any more to the iterator
- It is garbage collected
- What if we have a function that behaves the opposite?
- i.e. Mostly returns
Falseand we want to look for a singleTrueresult - For example
flip_is_tailsinvertsflip_is_heads
- i.e. Mostly returns
- To detect consecutive heads we can’t use
all- Instead use
any(another built-in) - Steps through an iterator
- Terminates after seeing the first
Truevalue - Like
anyalways returnsTrueorFalse
- Instead use
print("All falsey")
print(any([0, False, None]))
print(0 or False or None)
print("One truthy")
print(any([None, 3, 0]))
print(None or 3 or 0)All falsey
False
None
One truthy
True
3
- We can then rewrite our test for consecutive heads
import random
def flip_coin():
if random.randint(0, 1):
return "Heads"
else:
return "Tails"
def flip_is_tails():
return flip_coin() == "Tails"
all_heads = not any(flip_is_tails() for _ in range(20)) # iterator directly
print(all_heads)
def repeated_is_tails(count):
for _ in range(count):
yield flip_is_tails() # Generator
all_heads = not any(repeated_is_tails(20)) # generator expression is good
print(all_heads)False
False
- When to use
anyvsall?- Depends on the use case and which condition is more difficult to test
- To end early with a
Trueuseany - To end early with a
Falseuseall
- They are equivalent via De Morgan’s laws for Boolean logic
for a in (True, False):
for b in (True, False):
assert any([a, b]) == (not all ([not a, not b]))
assert all([a, b]) == (not any ([not a, not b]))Things to Remember
- The
allbuilt-in returnsTrueif all items provided are truthy- It stops processing input and returns
Falseonce a falsey item is encountered
- It stops processing input and returns
- The
anybuilt-in works similarly but with opposite logic- It returns
Falseif all items are falsey - Ends early with
Trueon a truthy value
- It returns
anyandallalways return the boolean valuesTrueorFalse- Unlike
orandandwhich return the last item necessary to test
- Unlike
- Using list comprehensions with
anyandallinstead of generators results in the full expression being evaluated before being tested- This defeats the point of using
anyandall - Use generators instead
- This defeats the point of using