True, False and Questions ⚖️
Before a program can decide anything, it has to ask a question that has exactly two possible answers. Here is how Python asks.
A third kind of value
sunny = True
raining = False
print(sunny, type(sunny))
print(raining)
True <class 'bool'>
False
Capital T, capital F, no quotes. "True" with quotes is a piece of text and
a completely different thing. The type is named bool after George Boole,
a Victorian mathematician who worked out the algebra of true and false about ninety
years before there was a computer to run it on.
Comparisons produce booleans
print(5 > 3)
print(5 < 3)
print(5 == 5) # equal? two equals signs!
print(5 != 5) # not equal
print(5 >= 5)
print("a" < "b") # alphabetical order
True
False
True
False
True
True
= assigns a value. == asks a question. Writing if x = 5 is a SyntaxError in Python, which is a kindness: in C it silently compiles and has caused decades of bugs.
Combining questions
age = 25
has_ticket = True
banned = False
print(age >= 18 and has_ticket)
print(age >= 65 or has_ticket)
print(not banned)
print(age >= 18 and has_ticket and not banned)
True
True
True
True
| Operator | True when | Think of it as |
|---|---|---|
and | both sides are true | the strict bouncer |
or | at least one side is true | the generous bouncer |
not | flips true to false | the contrarian |
Python uses the English words, not && and || like most
other languages. Read your conditions out loud: if the sentence makes sense in English,
it is probably right.
Chained comparisons, a genuine Python nicety
score = 75
print(0 <= score <= 100) # Python allows this and it means what you think
print(70 < score < 80)
True
True
Most languages force you to write score >= 0 and score <= 100. Python
lets you write it the way a mathematician would. It is one of those small touches that
make people fond of the language.
Truthiness: everything can answer the question
Python will happily treat any value as a yes or no. The rule is simple and worth committing to memory: empty and zero are false, everything else is true.
print(bool(0), bool(1), bool(-1))
print(bool(""), bool("a"), bool(" "))
print(bool([]), bool([1]))
print(bool({}), bool({"a": 1}))
print(bool(None))
False True True
False True True
False True
False True
False
This is why real Python code looks like this:
name = ""
if not name:
print("You did not give me a name.")
crew = ["Otis"]
if crew:
print(f"There are {len(crew)} aboard.")
You did not give me a name.
There are 1 aboard.
if crew: rather than if len(crew) > 0:. Shorter, and every
Python programmer reads it instantly. Note the space in bool(" ") is
True: a space is a character, so the string is not empty. That distinction
has ruined many a form validator.
None: the value that means "nothing here"
winner = None
print(winner)
print(winner is None)
print(type(winner))
None
True
<class 'NoneType'>
None is Python's way of saying "deliberately empty". It is not zero and not
an empty string; it is the absence of a value. Functions that do not return anything
return None, and it is the standard placeholder for "not decided yet".
== versus is
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # same contents?
print(a is b) # the very same object in memory?
print(a is c)
True
False
True
Two identical twins are equal but not identical. Point at one twin and give the finger a second name, and now you have two names for one person: that is 'is'. Python's == asks 'do these look the same', is asks 'are these literally the same thing'.
The rule in practice: use == for everything, except when comparing against
None, True or False, where the convention is
is. So: if value is None:, always.
Short circuits
def expensive_check():
print(" (this ran)")
return True
print("First:")
result = False and expensive_check()
print("Second:")
result = True or expensive_check()
print("Neither of those printed the marker, because Python stopped early.")
First:
Second:
Neither of those printed the marker, because Python stopped early.
With and, if the left side is false the answer is already false, so Python
never looks at the right side. This is not just an optimisation, it is a tool: it lets
you write if name and name[0] == "G": without crashing on an empty name,
because the second half never runs when the first half fails.
Can they ride?
A rollercoaster requires a height of at least 140cm, an age of at least 12, and that the rider is not currently holding a full mug of grog. Write the condition and test it with a few values.
Reveal solution
height = 152
age = 14
holding_grog = False
can_ride = height >= 140 and age >= 12 and not holding_grog
print(f"Cleared to ride: {can_ride}")
# and one who is not
print(f"Second rider: {130 >= 140 and 30 >= 12 and not False}")
Cleared to ride: True
Second rider: FalsePredict the truthiness
For each, say True or False before running.
print(bool("False"))
print(bool(0.0))
print(bool([0]))
print(bool(" "))
print(None == False)Reveal solution
True. It is a non-empty string. The contents are irrelevant.False. Zero is zero, even as a float.True. The list has one item in it. That the item is falsy does not matter.True. A space is a character.False. None is not False, it is None. This is exactly why the convention isis None.
Fix the login check
This is supposed to allow entry when the password matches and the account is not locked. It has two bugs.
password = "grog"
locked = False
if password = "grog" and locked == True:
print("Welcome")Reveal solution
Bug one: = instead of ==, which is a SyntaxError. Bug two: the logic is backwards; it demands the account is locked.
password = "grog"
locked = False
if password == "grog" and not locked:
print("Welcome")
Welcomenot locked rather than locked == False: shorter, and it reads like the sentence you would say out loud.