Why this matters
Every decision a program makes, whether to retry a request, which branch of code to run, when to stop a loop, comes down to an expression that is true or false. Python's comparison and logic operators are richer than they first look: comparisons can be chained like in math, and and or stop early and return actual values rather than just True/False, and every object can be used as a condition. These behaviors power common idioms such as defaults (name or "stranger") and guards (b != 0 and a % b == 0). Next module, if and while will put them to work.
Comparison operators
| Operator | Meaning |
|---|---|
== | equal value |
!= | not equal |
<, <= | less than, less or equal |
>, >= | greater than, greater or equal |
is, is not | same object, different object |
in, not in | membership (for strings, lists, ...) |
Each returns True or False:
print(3 == 3.0) # int and float compare by value
print("abc" == "ABC") # case matters
print("apple" < "banana")
print("Zebra" < "apple")
print("ell" in "hello")
True
False
True
True
True
Strings compare lexicographically by character code, like a dictionary but with all uppercase letters (codes 65-90) before all lowercase ones (97-122). That is why "Zebra" < "apple". Comparing incompatible types with < is an error: 3 < "4" raises TypeError: '<' not supported between instances of 'int' and 'str'. (== between unrelated types is allowed and simply returns False.)
Chained comparisons
Python lets you chain comparisons the way math does:
x = 15
print(10 <= x < 20)
print(1 < 2 < 3 < 4)
True
True
a < b < c means a < b and b < c, except that b is evaluated only once. Any comparison operators can be chained, which occasionally reads strangely: 1 < 3 > 2 is True because it means 1 < 3 and 3 > 2. Keep chains to the natural "in range" shape.
Logical operators: and, or, not
| Expression | Value |
|---|---|
not x | True if x is falsy, else False |
x and y | if x is falsy, x; otherwise y |
x or y | if x is truthy, x; otherwise y |
With booleans they behave like the truth tables you might know:
print(True and False, True or False, not True)
False True False
Precedence, lowest to highest: or, then and, then not, all below comparisons. So a < b and not c == d or e groups as ((a < b) and (not (c == d))) or e. Use parentheses when mixing and with or.
Truthiness
Any object can be used where a true/false value is expected. Python asks the object for its truth value, as bool(x) would. The built-in rule, from the conversion lesson:
falsy: False None 0 0.0 "" [] {} set() (and other empty containers)
truthy: everything else
print(not "")
print(not "hello")
print(not 0, not 7)
True
False
True False
Truthiness lets you write if name: instead of if name != "":, and if items: instead of if len(items) > 0:. Both idioms are standard Python.
and/or return operands, and short-circuit
This is the part that surprises people. and and or do not convert their result to a bool. They return one of the operands, and they evaluate the right operand only if needed:
x or y: evaluatex. If it is truthy, the answer isxandyis never evaluated. Otherwise the answer isy.x and y: evaluatex. If it is falsy, the answer isxandyis never evaluated. Otherwise the answer isy.
print(0 or 5)
print(3 or 5)
print(0 and 5)
print(3 and 5)
print("" or "default")
5
3
0
5
default
Not evaluating the right side when the answer is already known is called short-circuit evaluation. In the bytecode, it is a conditional jump: CPython evaluates the left operand, and if it already decides the result, jumps past the code for the right operand. You can see this with dis.dis("a or b"): there is a POP_JUMP_IF_TRUE-style instruction that skips LOAD_NAME b.
Short-circuiting gives two important idioms.
Defaults with or:
name = ""
display = name or "anonymous"
print(display)
anonymous
Careful: or treats every falsy value as missing. If 0 is a legitimate value (count = given or 10 with given = 0), you get 10 instead of 0. In those cases test explicitly with is None (using if, next module).
Guards with and: put a cheap safety check on the left so a dangerous operation on the right only runs when safe.
a, b = 10, 0
print(b != 0 and a / b > 2)
False
Without the guard, a / b would raise ZeroDivisionError. Because b != 0 is False, and returns False and never evaluates the division. Order matters: a / b > 2 and b != 0 crashes.
Equality versus identity, again
== compares values, is compares objects (lesson 2 of the previous module). For None, use is. For everything else, use ==:
x = None
print(x is None)
print([1, 2] == [1, 2], [1, 2] is [1, 2])
True
True False
Common mistakes
=instead of==. In a comparison context Python catches it:print(x = 5)givesTypeError: 'x' is an invalid keyword argument for print(), andif x = 5:givesSyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?.- English-style
or.color == "red" or "blue"is always truthy: it parses as(color == "red") or "blue", and"blue"is truthy. Writecolor == "red" or color == "blue", orcolor in ("red", "blue"). - Guard on the wrong side.
a % b == 0 and b != 0still divides by zero. The guard goes first. ordefaults that swallow 0 or empty strings when those are valid values.- Comparing floats with
==after arithmetic: usemath.isclose.
Recap
- Comparisons return booleans; strings compare by character code;
<between unrelated types is aTypeError. - Chains like
0 <= x < 10mean0 <= x and x < 10. - Precedence: comparisons, then
not, thenand, thenor. - Every object has a truth value: zero, empty and
Noneare falsy. and/orreturn an operand and evaluate the right side only when needed (short-circuit), enablingx or defaultandsafe_check and risky_operation.
Going further: run import dis; dis.dis("a and b or c") and trace the jump instructions. Read the Python reference section "Boolean operations".