Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~28 min· topic 8 of 11

Topic 1.8

Comparisons and Logic

In one line

Comparison operators (==, !=, <, >, <=, >=) ask questions and answer with True or False. and, or and not combine answers. == asks "same value?", is asks "same object?", and and/or stop early and return one of their operands.

Think of it like this

The rules at a cinema. "You may enter if you are 12 or older and have a ticket." The two small questions (age? ticket?) each have a yes/no answer, and the word "and" combines them. Python's comparisons ask the small questions; and, or and not combine the answers.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Comparison
An expression such as a < b that gives True or False.
Logical operator
and, or, not, which combine true/false answers.
Chained comparison
Several comparisons in a row, like 1 < x < 10.
Equality (`==`)
True when two values are equal.
Identity (`is`)
True when two names point to the very same object.
Short-circuit
Stopping as soon as the result is known, without running the rest.
Operand
One of the values an operator works on.
De Morgan's laws
Rules for turning not (a and b) into (not a) or (not b) and the other way round.

Step by step

01Asking questions

A comparison produces a bool you can print, store, or later use to choose what to do (Phase 2). Notice 3 == 3.0 is True: the comparison is about value, and 3 and 3.0 are the same number. A text "3" is a different kind of thing, so "3" == 3 is False, and Python does not complain.

main.pywhole filepython
age = 25
print(age > 18, age == 25, age != 25, age >= 30)    # True True False False
print("3" == 3, 3 == 3.0)                           # False True

02Chains

18 <= age < 30 asks whether age sits between 18 (included) and 30 (not included). Python evaluates age once and checks both sides, and stops if the first check is False. Most other languages need age >= 18 && age < 30.

main.pywhole filepython
print(18 <= age < 30, 18 <= age < 20)    # True False
print(1 < 2 < 3, 1 < 3 < 2)               # True False

03== versus is

Two lists with the same contents are *equal*, but they are two separate objects in memory, so is says False. same = first makes a second name for the same list, so first is same is True. (A list is a container of values written in square brackets; Phase 4 covers them.)

For None, there is only ever one None, so x is None is the exact, fast question.

main.pywhole filepython
first = [1, 2]
second = [1, 2]
same = first
print(first == second, first is second, first is same)   # True False True
== versus isdiagram
Rendering diagram…

04and, or, not and their order

not runs first, then and, then or. Use parentheses when you mix them, because few people remember the order. 1 or 2 and 0 is 1 or (2 and 0): 2 and 0 is 0, and 1 or 0 is 1.

main.pywhole filepython
print(True and False, True or False, not True)   # False True False
print(1 or 2 and 0)                               # 1

05Short-circuit and returning operands

In True or 1 / 0 Python reads the left side, sees that it is true, and never looks at 1 / 0, so no error. In False and 1 / 0 the left side is already false, so again the right side is skipped.

Because of this, or is used to pick a default: name or "stranger" is the name if it is not empty, otherwise "stranger". Be careful when 0 or an empty list is a legitimate value; use if x is None instead (Topic 1.4).

main.pywhole filepython
print("" or "default")         # default
print("Asha" or "default")     # Asha
print(0 and 5, 3 and 5)        # 0 5
print(True or 1 / 0)           # True   (no ZeroDivisionError)
Short-circuit and returning operandsdiagram
Rendering diagram…

06De Morgan's laws

"Not (adult and has ticket)" means "not an adult, or no ticket". The rule: pushing not inside flips and to or and or to and. This lets you rewrite a confusing condition into an easier one; the two forms always give the same answer, as the examples show.

main.pywhole filepython
a = True
b = False
print((not (a and b)) == ((not a) or (not b)))     # True
print((not (a or b)) == ((not a) and (not b)))     # True

Try it yourself

  1. 1

    Between two numbers

    Set temperature = 22 and print 20 <= temperature <= 25. Change it to 30 and 10 and check each time.

  2. 2

    Predict the operand

    Before you run, predict 0 or 7, 5 or 7, 0 and 7, 5 and 7 and "" or "x". Check yourself.

  3. 3

    Prove De Morgan

    Set a and b to each of the four combinations of True and False, and check the first law holds every time.

Code & diagrams

Comparisons, chains, == versus is New tab

Note first == second is True but first is second is False.

Sign in to run this example in your browser.

Expected output

True True False False
True False
True False
False True False
True True True True False
True False True
True False
Short-circuit and operands New tab

The two True or / False and lines would crash if the right side ran.

Sign in to run this example in your browser.

Expected output

default
Asha
0 5
last
stranger
True
False
True
True
True False True
1
Defaults: when 0 and empty text are real answers New tab

or replaces every falsy value, so a retries setting of 0 and an empty greeting are lost. Testing is None keeps them. The last lines show nan inside a list and a chain. The def lines are functions (Phase 5): read each as a named recipe that gives back one value. The dictionary is Phase 4.

Sign in to run this example in your browser.

Expected output

3 0
'Hello' ''
30 30
False True False
True True True True

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use is with a number

Compare a variable to a number with is.

main.pywhole filepython
x = 5
print(x is 5)
terminal
$ python main.py
── what you'll see ──
main.py:2: SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
print(x is 5)
True

Break #2

Put = where you meant ==

Write a comparison with one equals sign inside print.

main.pywhole filepython
age = 12
print(age = 12)
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 2, in <module>
print(age = 12)
~~~~~^^^^^^^^^^
TypeError: print() got an unexpected keyword argument 'age'

Myth vs fact

Myth

a == b and a is b mean the same thing.

Fact

== compares values; is compares identity (the very same object). Equal values can still be separate objects.

Myth

and and or always return True or False.

Fact

They return one of their operands. "" or "default" is "default", and 3 and 5 is 5.

Myth

0 < x < 10 is a mistake because it compares a bool.

Fact

Python chains comparisons. It means 0 < x and x < 10, with x evaluated once.

Myth

"Z" < "a" is False because Z comes later in the alphabet.

Fact

Strings compare by character code, and all capitals come before small letters. "Z" < "a" is True.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    x is None compiles to a single pointer comparison, which is the fastest check there is. x == None calls x.__eq__, which a class may override.

  • ▸

    Short-circuiting is used for safe guards: obj is not None and obj.value > 3 never reads .value on None. The same guard pattern shows up in if user and user.is_admin: throughout web code.

  • ▸

    NumPy arrays and pandas columns do not support and/or on whole arrays. They use &, | and ~ with parentheses, because and needs a single True or False (Phase 14).

  • ▸

    Floating-point comparisons need math.isclose (Topic 1.3). Every ordering and equality comparison with nan is False, and only nan != nan is True. A subtle exception: containers check identity before equality, so nan in [nan] is True and [nan] == [nan] is True when the very same nan object is in both, but float("nan") in [float("nan")] is False.

  • ▸

    is on numbers and strings is implementation behaviour. CPython keeps one object for each int from -5 to 256, and the compiler may merge equal constants in one code block, so a = 1000; b = 1000; a is b can be True in one file and False at the prompt. Interviewers ask this to see if you know the answer is "undefined, use ==". Since Python 3.8 the compiler warns about is with a literal (SyntaxWarning).

  • ▸

    A chained comparison evaluates each middle operand once and short-circuits: a < f() < b calls f() one time, and if a < f() is False the last comparison never runs. It is not limited to numbers or to one kind of operator: x == y == z, a < b > c and 0 < x is not None are all legal, which is also why a == b is True does not mean what beginners expect. and/or return operands, but not always returns a real bool.

Remember this

  1. 1

    The comparison operators are == (equal), != (not equal), <, >, <= and >=. Each gives a bool. Do not confuse == (a question) with = (an assignment): age = 12 stores, age == 12 asks. Numbers compare by value, so 3 == 3.0 is True; text and numbers never match: "3" == 3 is False.

  2. 2

    Python allows chained comparisons: 18 <= age < 30 means 18 <= age and age < 30, and it reads like maths. 1 < 3 < 2 is False. Strings compare alphabetically by character code: "apple" < "banana" is True, but "Z" < "a" is also True, because all capital letters have lower codes than small letters.

  3. 3

    == asks whether two values are equal; is asks whether two names point to the same object (an object is one value stored in memory). Two separate lists [1, 2] and [1, 2] are == but not is the same. Use == for values, and is only for the singletons None, True and False (values of which only one copy exists), as in x is None. x is not None is the readable negation.

  4. 4

    and, or, not combine booleans. A and B is true only if both are. A or B is true if at least one is. not A flips. In a mixed expression not runs first, then and, then or, so 1 or 2 and 0 is 1 or (2 and 0).

  5. 5

    and and or short-circuit: they stop as soon as the answer is known. True or 1 / 0 does not divide, because or already knows the answer is true. They also return one of the original values, not just True or False: "" or "default" is "default", and 3 and 5 is 5. The rule: or returns the first truthy operand (or the last), and returns the first falsy operand (or the last).

  6. 6

    That behaviour gives a handy idiom for defaults: name or "stranger". It also contains a trap: it replaces every falsy value, including a valid 0. De Morgan's laws help simplify conditions: not (a and b) equals (not a) or (not b), and not (a or b) equals (not a) and (not b).

Explain it without notes

01

Explain the difference between == and is, and when each is correct.

02

What is short-circuit evaluation? Give an example that would crash without it.

03

What does a or b return in Python, and why is name or "stranger" risky for numbers?

04

State De Morgan's laws and show an example in code.

Practice

01

Set age = 15 and print whether it is in the teenage range 13 to 19 (inclusive) with a chained comparison.

02

Set nickname = "" and print nickname if it has text, otherwise Guest, using or.

03

Set raining = True, has_umbrella = False. Print whether you get wet: you do if it rains and you have no umbrella.

Trade-offs

  • ↔

    **or defaults vs. explicit checks.** x or default is short but replaces every falsy value; default if x is None else x (Topic 2.2) is longer and exact.

  • ↔

    Chained vs. spelled-out comparisons. 0 < x < 10 is shorter and closer to maths. Spelled-out comparisons may be clearer to someone from another language, but are no more correct.

Done when you can

  • Done when you can use all six comparison operators and a chain.

  • Done when you can explain == versus is and use is None.

  • Done when you can predict what or and and return for any two values.

  • Done when you can explain short-circuiting with an example.

  • Done when you can rewrite not (a and b) using De Morgan's law.