Python Module 2 – Values, variables, numbers and strings
Booleans, None, truthiness and comparisons
Predict which Python values are truthy or falsy, use and, or and not knowing they return operands, and compare correctly with ==, is and chained comparisons.
What you will learn
- Predict the truthiness of common values
- Use and, or and not, knowing they return operands
- Compare with ==, is and chained comparisons correctly
Before you start
On this page
Programs keep asking yes-or-no questions: is the list empty, did the user type anything, is the score in range?
Python’s answers are the two values True and False, but if, while, and, or and not accept almost any
value, because nearly every object counts as either true or false. This lesson shows which values count as which, what
and and or really return, how None fits in, and how to compare values without the classic traps.
True, False and the bool type
True and False are the only two objects of type bool. Comparisons such as 3 > 2 produce them, and
bool(value) converts any value to one of them. Under the hood, bool is a kind of int, with True worth 1 and
False worth 0:
print(True == 1, False == 0)
print(True + True)
print(sum([True, False, True])) # adding bools counts the True values
print(isinstance(True, int)) # bool is a kind of int
print(bool("False")) # careful: any text that is not empty is true Output
True True 2 2 True True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bool_is_int.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
That is why sum() of a list of bools counts the True values. Python’s documentation still discourages relying
on bools behaving as numbers: when you mean a number, convert with int(flag). The last line is a common mistake
when reading answers from users or files: bool("False") is True, because any string that is not empty is true.
Compare the text instead, as in answer == "yes".
Version note
Using ~ (bitwise “not”) on a bool has been deprecated since Python 3.12 and is due to become an error in Python
3.16: ~True is the int -2, not False. Write not flag.
Truthiness: which values count as true
In an if or while condition, and as an operand of and, or and not, Python treats a value as falsy or
truthy. The falsy values are few and worth knowing by heart:
NoneandFalse;- zero of any number type:
0,0.0,0j(andDecimal(0)andFraction(0)from the standard library); - empty strings and empty collections:
"",(),[],{},set(),range(0).
Everything else is truthy, unless its class defines otherwise (classes are a later module).
values = [None, False, 0, 0.0, "", [], {}, "0", " ", "False", [0], float("nan")]
for value in values:
print(repr(value).ljust(8), bool(value)) Output
None False
False False
0 False
0.0 False
'' False
[] False
{} False
'0' True
' ' True
'False' True
[0] True
nan True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 truth_table.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The last five values hold the surprises: the strings "0", " " and "False" are not empty, the list [0] has
one item, and NaN is a float that is not zero, so all five are truthy. In practice, truthiness gives short, readable tests:
PEP 8 recommends if items: and if not items: rather than if len(items):.
Version note
New in Python 3.14: NotImplemented, a special value that the comparison and arithmetic methods of classes return
when they cannot handle an operand, raises TypeError when it is used as a condition. Python 3.9 to 3.13 only
warned about it.
and, or and not
and and or evaluate their operands from left to right and stop as soon as the answer is known. They do not turn
the answer into a bool: the result is the operand that decided it.
How or and and choose their result
Text description of the diagram
Two small flowcharts, one above the other. Both start by testing whether x is true (truthy).
- x or y: if x is true, the result is x and y is not evaluated at all. If x is false, Python evaluates y and the result is y.
- x and y: if x is false, the result is x and y is not evaluated at all. If x is true, Python evaluates y and the result is y.
In both cases the result is one of the two operands, not necessarily True or False.
print("" or "guest") # or gives the first operand that is true ...
print("Asha" or "guest")
print(0 or None) # ... or the last one, when none is true
print([] and 10) # and gives the first operand that is false ...
print(5 and 10) # ... or the last one, when none is false
print(0 and 1 / 0) # 0 is false, so 1 / 0 is never evaluated
print(not "text", not 0) # not always gives True or False Output
guest Asha None [] 10 0 False True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 and_or_values.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
x or ygivesxifxis true, and otherwisey. So"" or "guest"is"guest", a handy default for empty text.x and ygivesxifxis false, and otherwisey. In0 and 1 / 0, the 0 already decides the answer, so the division never runs and there is noZeroDivisionError: this is called short-circuiting.notis different: it always returnsTrueorFalse.
A common mistake: or throws away a valid zero
The or default treats every falsy value as missing, and that includes 0 and the empty string, which are often
perfectly good answers:
saved_volume = 0 # the user turned the sound off on purpose
volume = saved_volume or 50 # bug: the 0 is falsy, so it is replaced
print(volume)
volume = 50 if saved_volume is None else saved_volume # only None means "not set"
print(volume) Output
50 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 zero_trap.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The user chose silence, and saved_volume or 50 turned it back up to 50. The fix tests for the one value that
really means “not set”. a if condition else b is a conditional expression: it evaluates the condition and gives
a when it is true and b otherwise (the next lesson places it among the operators).
None, the “no value” value
None is the only object of its type, NoneType, and it stands for “no value here”: a setting that was not given, a
result that does not exist, a search that found nothing. Functions that return nothing in particular return None:
result = print("Hello") # print() shows the text and returns None
print(result)
print(result is None)
print(type(None))
print(None == 0, None == False, not None) # falsy, but equal to neither Output
Hello None True <class 'NoneType'> False False True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 none_basics.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
- Test for it with
is Noneandis not None, as PEP 8 recommends: there is exactly oneNone, andischecks for that very object, which no class can redefine. Noneis falsy, but it is not equal to0or toFalse. Writevalue is None, notnot value, when 0 or an empty string are valid values.Nonetakes part in no arithmetic and no ordering, so a missing value stops a program the moment it is used:
scores = [72, None, 85] # one student was absent
print(max(scores)) Output (exit status 1)
Printed as an error (standard error)
Traceback (most recent call last):
File "missing_score.py", line 2, in <module>
print(max(scores))
~~~^^^^^^^^
TypeError: '>' not supported between instances of 'NoneType' and 'int'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missing_score.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
max() compares the items with >, and None cannot be compared with a number. Leave missing values out, or
replace them, before you compute with the list.
Comparisons
Python has four kinds of comparison:
==and!=compare values:1 == 1.0isTrue.<,<=,>and>=compare order:"apple" < "banana"isTrue.isandis notcompare identity, whether both sides are the very same object:None is NoneisTrue.inandnot intest membership, which the next lesson covers:"a" in "cat"isTrue.
With the built-in types, all of them give a bool. They share one precedence level, and you can chain them:
0 <= marks <= 100 means 0 <= marks and marks <= 100, with marks evaluated once, and Python stops at the first
part that is false.
marks = 75
print(0 <= marks <= 100) # means 0 <= marks and marks <= 100
print(1 < 2 < 3, 3 > 2 > 1)
print(1 < 2 == True) # chained too: 1 < 2 and 2 == True
print((1 < 2) == True) # brackets end the chain
print(1 == 1.0, "1" == 1) # numbers compare by value; text never equals a number
print("apple" < "banana", "Zebra" < "apple") # text compares by code point Output
True True True False True True False True True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 chained.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
- Chaining applies to every comparison operator, which is what makes
1 < 2 == Truea puzzle: it means1 < 2 and 2 == True, and 2 is not equal toTrue(which is 1), so the whole expression isFalse. Brackets end the chain. Better still, never compare withTrueorFalseat all: writeif done:. - Numbers of different types compare by value, so
1 == 1.0isTrue; a string is never equal to a number, and ordering a string against a number raisesTypeError. - Strings are ordered character by character by their code points, in which the capitals A to Z all come before the
small letters a to z:
"Zebra" < "apple".
== or is?
== asks whether two objects have the same value; is asks whether they are the same object. Two lists built
separately are equal but not identical:
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # the same value
print(a is b) # but two different lists
c = a
print(c is a) # two names for one list
answer = None
print(answer is None) # the right way to test for None Output
True False True True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 is_vs_eq.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Use is for None (and for the rare object you create only to compare identity with), and == for everything
else. For numbers and strings, Python may or may not reuse one object for equal values, depending on the version and
on how the code was compiled, so is with them gives unreliable answers. Python 3.8 and newer warn when they see it
used with a literal:
count = 300
print(count is 300) # Python warns: is compares identity, not value
print(count == 300) Output
True True
Printed as an error (standard error)
is_literal.py:2: SyntaxWarning: "is" with 'int' literal. Did you mean "=="? print(count is 300) # Python warns: is compares identity, not value
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 is_literal.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The program runs and the warning appears on standard error. The True from count is 300 comes from how CPython
compiled this file, not from any rule of the language; a different program could print False.
Key takeaways
TrueandFalseare a kind of int (1 and 0), but convert withint()when you mean a number.- Falsy:
None,False, zeros, and empty strings and collections. Everything else, including"0","False"and[0], is truthy. andandorshort-circuit and return one of their operands;notalways returns a bool.x or defaultalso replaces 0 and""; testx is Nonewhen onlyNonemeans “missing”.- Chained comparisons such as
0 <= marks <= 100read like maths, and every comparison operator chains. - Use
==for values andisonly forNoneand other single objects; never compare withTrueorFalse.
Exercise
Exercise · Easy · Python
Turn marks into a letter grade
Write grade(marks), which turns marks out of 100 into a letter grade on this scale (the cut-offs are for this exercise; every college publishes its own):
- from 90 to 100:
"O" - from 80 up to, but not including, 90:
"A+" - from 70 up to 80:
"A" - from 60 up to 70:
"B+" - from 50 up to 60:
"B" - from 40 up to 50:
"C" - from 0 up to 40:
"F"
Marks may be whole numbers or have decimals: grade(89.5) is "A+", and grade(90) is "O". For marks below 0 or above 100, raise ValueError. Use chained comparisons such as 80 <= marks < 90; the sample tests check every cut-off from both sides.
Starter code · grades.py
def grade(marks):
"""Return the letter grade for marks from 0 to 100; raise ValueError outside that range."""
# Replace this line with your code.
return "F" The sample tests · test_grades.py
from grades import grade
def raises_value_error(marks):
"""True when grade(marks) raises ValueError."""
try:
grade(marks)
except ValueError:
return True
return False
def test_top_grade():
"""gives O from 90 up to 100"""
assert grade(100) == "O"
assert grade(95.5) == "O"
assert grade(90) == "O"
def test_middle_grades():
"""gives A+, A, B+ and B from their cut-offs up to just below the next one"""
assert grade(89.99) == "A+"
assert grade(80) == "A+"
assert grade(79.5) == "A"
assert grade(70) == "A"
assert grade(69) == "B+"
assert grade(60) == "B+"
assert grade(59.9) == "B"
assert grade(50) == "B"
def test_lower_grades():
"""gives C from 40 and F below 40, down to 0"""
assert grade(49.5) == "C"
assert grade(40) == "C"
assert grade(39.99) == "F"
assert grade(0) == "F"
def test_out_of_range():
"""raises ValueError for marks below 0 or above 100"""
assert raises_value_error(-1)
assert raises_value_error(-0.5)
assert raises_value_error(100.5)
assert raises_value_error(101) A hint
Check the range first: if not 0 <= marks <= 100: catches both kinds of bad marks in one test, and raise ValueError("…") stops the function. Then test the grades from the top down with if and elif; each test is a chained comparison such as 80 <= marks < 90, which is true for 80 and for 89.99 but not for 90.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- Truth value testing (Python Software Foundation)
- Boolean operations, and, or, not (Python Software Foundation)
- Boolean type, bool (Python Software Foundation)
- Expressions: comparisons (Python Software Foundation)
- Expressions: Boolean operations (Python Software Foundation)
- Built-in constants, None (Python Software Foundation)
- Data model: objects, values and types (Python Software Foundation)
- What's New in Python 3.14 (Python Software Foundation)
- What's New in Python 3.8: changes in Python behaviour (Python Software Foundation)
- PEP 8: programming recommendations (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress