Basic Programming Constructs · branching logic, truth tables, and tracing the path your code actually takes
In your own words: what does an if statement let a program do that it couldn't do by just running one line after another?
A conditional, often called an if statement, runs a chunk of code only when some
boolean test is True. This section covers conditionals in Python.
Python evaluates the TEST. If, and only if, it is True, Python runs the indented
BODY. If the test is False, Python simply skips it and moves on to whatever comes next.
if TEST:
BODY
The test is n < 0. Since return exits the function immediately, line 4 only runs when the test was False.
The body of a conditional can be several lines, all indented the same amount. 'A' prints on every call, but
'B' only prints when the if body actually runs.
When the condition is simple and the body is one short line, you can put both on the same line. This can save space, but it also makes code harder to scan.
if x > 10: print('yes!')
def f():
if True:
print('a')
print('b')
print(f())
What is printed by this code?
aa then ba then b then Nonea then Nonef has no return, so once it prints a and b, the outer print(f()) still prints whatever f returned: None.
if TEST: only runs its indented body when TEST is True; otherwise Python skips straight past it.if TEST: BODY shortcut exists, but save it for the simplest cases.
A plain if only handles the True case. An if-else statement adds a second body that runs
whenever the condition is False, so every possibility is covered in one clean structure.
Python runs the else body only when the if's condition was False. Exactly one of the two bodies runs, never both, never neither.
def f():
if False:
print('a')
else:
print('b')
print(f())
What is printed?
a then ba then NoneNoneb then Noneelse body runs only when the paired if's condition was False.if-else always covers both possibilities.
What if there are three cases instead of two? Say we want negative, positive, and
zero. A nested conditional, an if inside the body of another if, handles it.
Python only enters the else body when n < 0 was False. Only then does it check the nested n > 0.
The comment # n is either positive or zero isn't decoration. It records what we can safely assume once we've reached that line:
the outer test already ruled out negative values.
if splits the world into "negative" and "everything else". The inner if then splits "everything else" into "positive" and "zero". Nesting is how you narrow down step by step.
def f(x, y):
if x:
if y:
return 'a'
else:
return 'b'
else:
return 'c'
What does f(True, False) return?
'a''b''c'Noneif can live inside the body of another if or else, letting you split cases into finer and finer pieces.
Nesting an if inside every else is so common that Python gives it a shortcut: elif, short for
"else if". It reads top to bottom instead of drifting rightward.
if, then each elif, in order.True, Python runs that body and skips every remaining test.True, Python runs the else body, if there is one.if TEST1:
BODY1
elif TEST2:
BODY2
else:
BODY3
This is exactly the nested version from before, rewritten with elif. Same behavior, no rightward drift.
You can chain as many elifs as you need. Python still checks them strictly in order and stops at the first match.
else is optionaldef signString(n):
if n < 0:
return 'Negative'
elif n > 0:
return 'Positive'
print(signString(0))
There's no else. What happens for signString(0), where neither test is True?
SyntaxError, a chain must end with else.None.if test one more time before giving up.
letterGrade(score) is defined on the next slide, with one if, two elifs, and an else.
For each call on the next slide, work out which single branch actually runs, drag (or click, then click the box) its return value into place.
As you go, the branch you just traced lights up in the code, so you can see the execution path that call took. Each new correct answer moves the highlight to its own branch.
All four calls traced. Each call took exactly one path through the if-elif-else chain, and Python stopped checking the moment it found a match. Only the most recent branch stays highlighted, since only one path runs per call.
True, skipping the rest.elifs after an if; the trailing else is optional.else, if every test is False, Python falls through silently, no crash, just nothing runs.
An if-else statement runs one of two bodies. An if-else expression instead
evaluates to one of two values, in a single line.
Look closely at the order: the value comes first, then the test, then the fallback value. That's the opposite order from an if-else statement, which can feel backwards at first.
TRUE_VALUE if TEST else FALSE_VALUE
This if-else expression is exactly equivalent to the if-else statement we just saw. Use expressions like this sparingly, only when they clearly make code easier to read.
x = 5
print('a' if x == 0 else 'b')
What does this print?
Noneba if x == 0 else baTRUE_VALUE if TEST else FALSE_VALUE, note the value comes before the test.if-else expression evaluates to a single value that you can use anywhere a value is expected.
Conditions using and and or don't always evaluate both sides. Python short-circuits: it stops as soon
as it knows the overall answer, and this can be surprisingly useful.
Before you run it, predict: does this code crash? If not, what does it print?
print((1 == 2/0) and (3 == 4))
Python evaluates the left side of and first. 1 == 2/0 divides by zero before the right side is ever considered. Once you've seen it crash, edit the code to swap the two sides of and and run it again.
print((1 == 2/0) and (3 == 4)) # run this, it crashes. Then swap the sides and run again.
print((1 == 2/0) and (3 == 4)) # crashes!
print((3 == 4) and (1 == 2/0)) # prints False, no crash!
(3 == 4), which is False.False and anything is always False, no matter what the right side is.1 == 2/0. It short-circuits straight to False.
or works the same way, just aimed at the opposite outcome. As soon as Python finds a True (or even just truthy) left side, it stops, since True or anything is always True.
print((1 < 2) or (3 < 4/0)) # True, no crash
print((1 > 2) or (3 > 4/0)) # crashes!
Say score is usually a number, but is sometimes None. Testing score > 100 directly would crash whenever score is None.
if score > 100:if (score != None) and (score > 100):When score is None, score != None is False, and and short-circuits before ever comparing None > 100.
What does (3 > 4) and (5 == 1/0) evaluate to?
NoneTrueFalseWhat does (3 < 4) and (5 == 1/0) evaluate to?
FalseTrueNoneand stops as soon as it sees a False left side; the right side is never evaluated.or stops as soon as it sees a True (or truthy) left side.(x != None) and (x > 100) avoid a crash, but use short-circuiting sparingly, only when it makes code clearer.
A Boolean expression is an expression that always evaluates to True or False, no matter what values go in.
Once more than one or two variables are involved, it can be easy to lose track of every possible case by eye. A truth table solves that: it lists every combination
of inputs in its own row, with a final column showing what the expression evaluates to for that combination of inputs.
With two variables, there are four possible combinations. Let's step through building each row.
In row 1:A is True and B is True, so (A or B) is (True or True), which is True.
In row 2:A is True and B is False, so (A or B) is (True or False), which is True. Only one side needs to be True.
In row 3:A is False and B is True, so (A or B) is (False or True), which is True.
In row 4:A is False and B is False, so (A or B) is (False or False), the only case where or is False.
| A | B | (A or B) |
|---|---|---|
| True | True | True |
| True | False | True |
| False | True | True |
| False | False | False |
For a more complex expression, add a column for each subexpression first. Here, not B is computed before the final column,
which keeps the last step simple and less error-prone.
| A | B | not B | (A and (not B)) |
|---|---|---|---|
| True | True | False | False |
| True | False | True | True |
| False | True | False | False |
| False | False | True | False |
If the last column of two truth tables matches exactly, the expressions are equivalent, and you can safely swap one for the other. The mathematician Augustus De Morgan proved two especially useful equivalences:
not through an or/and flips it to the other operator, and negates each side.
A restaurant is closed after noon and all day on Monday. (hour > 12) or (day == 'Monday'). To check whether it is open, you need the opposite of that, the not of the whole thing, and that's the tangled condition shown.
De Morgan's first law lets you push the not inward instead of leaving it wrapped around everything: or becomes and, and each side gets its own not.
Each negated comparison simplifies on its own.
not ((hour > 12) or (day == 'Monday'))
(not (hour > 12)) and (not (day == 'Monday'))
(hour <= 12) and (day != 'Monday')
Which of the following is NOT one of De Morgan's Laws?
(not (A and B)) is equivalent to ((not A) or (not B))(A and B) is equivalent to ((not A) or (not B))(not (A or B)) is equivalent to ((not A) and (not B))
On the next slide, each chip is a Boolean expression, some built straight from True/False, some as De Morgan pairs of each other.
Drag (or click, then click the bin) each one into True or False, based on what it actually evaluates to.
All 8 expressions sorted. Notice the De Morgan pairs, like not (True or False) and (not True) and (not False), always land in the same bin.
not (A or B) ≡ (not A) and (not B), and not (A and B) ≡ (not A) or (not B).Conditionals are powerful enough that Python lets you write them in some genuinely bad ways. Here are the common traps, so you recognize them in other people's code, and avoid them in your own.
A common newcomer mistake: writing a second, independent if when a single if-elif or if-else was the better choice.
They may run identically, but they are not equally good code.
if n < 0:
return 'Negative'
else:
return 'Non-negative'
if n < 0:
return 'Negative'
if n >= 0:
return 'Non-negative'
else, it's obvious every value is covered. With two separate ifs, you have to reason through both tests yourself.if-else version runs one test. The two-if version always runs both, even though the second is redundant.n = 5
if n > 0:
n = n - 10
if n < 0:
print('Negative') # this runs too!
n starts at 5, positive, so the first body runs and changes n to -5. The second if then tests that new value, finds it negative, and runs too. So this doesn't just run a redundant extra test, it can silently produce a completely different result than an if-elif chain would, which only ever checks the original value once.
You can chain if-else expressions together. It "works", but it's confusing and hard to read, which makes it easy to introduce bugs.
return 'Negative' if n < 0 else 'Positive' if n > 0 else 'Zero'
if-else expressions in the simplest cases, and never nest them. For anything with real complexity, including nested logic, reach for an if-else or if-elif-else statement instead.
Both versions print the same score. Python automatically converts True to 1 and False to 0 in arithmetic,
so (strength > 20) * 10 silently "works". Run it and compare.
int(True) is 1 and int(False) is 0. You can even drop the explicit int() call, since Python converts booleans for you inside arithmetic.
score = 50 + (strength > 20) * 10 has to notice the boolean-arithmetic trick just to realize there's branching logic here at all. Write the if statement instead.
'' is falsy, so or short-circuits past it and returns 'abc'. This is the same truthy/falsy conversion from
bool(), just applied inside and/or instead of an explicit if.
name = name or 'Unknown', a non-Boolean hiding inside or.name = 'Unknown' if name == '' else name, an honest Boolean test.if name == '': name = 'Unknown', the clearest of the three, and it never reassigns name to itself.def testSign(n):
if n < 0:
return 'Negative'
if n > 0:
return 'Non-negative'
This also has a subtler bug than style: what does it return for n == 0? But first, spot the improper use of conditionals.
if should be an elif or else, the two tests are meant to be mutually exclusive alternatives.return statement.n < 0 should be n <= 0.if when elif or else says the same thing more clearly and runs faster.if-else expressions; use an if-elif-else statement once things get complex.and/or. Both "work" but conceal the conditional from readers.True, or picks between two bodies based on it.elif is usually the cleaner, flatter choice.and/or skip work, and even guard against crashes.