A computer program is like a journey for your data. Data encounters situations that impact it during this journey, changing it forever. Like any journey, one has a choice of routes to reach the destination. Sometimes, data takes one path; sometimes, another. Which roads the data takes depends on the program's end goal.
When writing programs, you want your data to take the correct path. You want it to turn left or right, up, down, reverse, or proceed straight ahead when it's supposed to. We call this flow control.
How do we make data do the right thing? We use conditionals.
A conditional is a fork, or multiple forks, in the road. When your data arrives at a conditional, Python evaluates the conditional and tells the data where to go. The simplest conditionals use a combination of if
statements with comparison and logical operators (<
, >
, <=
, >=
, ==
, !=
, and
, or
, and not
) to direct traffic. They use the keywords if
, elif
, and else
.
That's enough talking; let's write some code. Create a file named conditional.py
with the following content:
value = int(input('Enter a number: '))
if value == 3:
print('value is 3')
Run conditional.py
at least twice.
3
.
This example shows the simplest of if
statements: it has a single block (one or more Python statements or expressions) that executes when the condition (value == 3
) is True
. Otherwise, Python bypasses the block. Regardless, execution eventually resumes with the first statement or expression after the if
statement.
Thus, if the input value is 3
, this code prints value is 3
. The code doesn't print anything if the user inputs any other integer.
We can expand the if
statement to include some code that runs when value
is not 3
:
value = int(input('Enter a number: '))
if value == 3:
print('value is 3')
# highlight
else:
print('value is NOT 3')
# endhighlight
Here, Python prints value is 3
if the user enters 3
; otherwise, it prints value is NOT 3
.
Note that the else
block isn't a proper statement; it's part of the if
statement.
We can nest if
statements inside an outer if
. In this next example, we nest an if
statement inside the else
block of the outer if
:
value = int(input('Enter a number: '))
if value == 3:
print('value is 3')
else:
# highlight
if value == 4:
print('value is 4')
else:
print('value is NOT 3 or 4')
# endhighlight
This time, run conditional.py
at least three times:
The indentation levels show how the code is supposed to work. In this case, Python:
value is 3
if the user inputs 3
.
value is 4
if the user inputs 4
.
value is NOT 3 or 4
if the user enters any other integer.
The sequence of operations begins on line 3, where we compare the user input against 3
. If yes, line 4 runs; otherwise, we drop down to the else
block. In the else
block, we compare the input against 4
on line 6. If yes, line 7 runs; otherwise, we drop down to the inner else
block and run the code on line 9.
We recommend avoiding nested if
statements when possible. They quickly become difficult to read with multiple levels of nesting or longish code blocks. However, don't get twisted up trying to avoid them entirely. Keep the nesting to a modest 2 or 3 levels deep and use functions to isolate some of the more complex code.
You can rewrite the previous example using an elif
block:
if value == 3:
print('value is 3')
elif value == 4:
print('value is 4')
else:
print('value is NOT 3 or 4')
The elif
block runs if value == 3
is False
and value == 4
is True
. The code produces the same results as the nested if
.
You can have as many elif
blocks as you need, but they all need to be after the if
block and, if the code has one, before the else
block. The elif
conditionals are evaluated in the order they appear in the code.
Finally, if
statement blocks may contain as many lines as you need:
if value == 3:
print('value is 3')
print('value is an odd number')
print('value is a prime number')
elif value == 4:
print('value is 4')
print('value is an even number')
print('value is NOT a prime number')
elif value == 9:
print('value is 9')
print('value is an odd number')
print('value is NOT a prime number')
else:
print('value is something else')
Every once in a while, you may want to create a block in an if
statement that does nothing. We usually do this for readability purposes. However, blocks can't be empty. Instead, you have to use a pass
statement.
if value == 3:
print('value is 3')
elif value == 4:
print('value is 4')
elif value == 9:
pass # We don't care about 9
else:
print('value is something else')
Adding a comment to a pass
is good practice so future programmers know why it is there.
All statements in a block must be indented from the statement that begins the block. The indentation in a block must be consistent. If the first line of the block is indented 4 spaces, all statements in the block must be indented 4 spaces.
Nested blocks should be indented more than the containing block. For instance, if the current block is indented by 4 spaces from the outer block (the conventional amount of indentation), then a nested block inside that block should be indented by 8 spaces. Another nested block would bring the indentation to 12 spaces.
Be careful with your indentation. If you accidentally outdent some code, that will end the block. For instance:
if value == 1:
print('value is one')
print('the value is odd')
If you meant to print both messages when value
is 1
, that's what will happen. However, Python will display the second message even if value
is not 1.
Let's look at the comparison operators in some more depth so you can build more complicated conditional statements. Remember that comparison operators return a Boolean value: True
or False
.
The expressions to the left and right of an operator are its operands. For instance, the equality comparison x == y
uses the ==
operator with two operands, x
and y
.
==
The equality operator returns True
when the operands have equal values, False
otherwise. We discussed ==
briefly in the Basics Operations chapter. It should be familiar, even if it still looks strange.
print(5 == 5) # True
print(5 == 4) # False
print('abc' == 'abc') # True
print('abc' == 'abcd') # False
print(5 == '5') # False
print([1, 2, 3] == [1, 2, 3]) # True
print([1, 2, 3] == [3, 2, 1]) # False
In most cases, operands must have the same type and value to be equal. Thus, 5
is not equal to '5'
. There are some places where you can mix types, however. For instance, integers and floats that are mathematically equivalent are usually, but not always, considered equal:
print(5 == float(5)) # True
big_num = 12345678901234567
print(float(big_num) == big_num) # False
Enormous floats lack precision at around 18 significant digits on most modern machines. That can lead to surprises if you happen to work with big numbers.
Comparisons with strings are case-sensitive. Thus, 'abc'
is not equal to 'aBc'
. You can use the str.lower
and str.upper
methods to achieve a case-insensitive comparison:
print('abc' == 'aBc') # False
print('abc'.lower() == 'aBc'.lower()) # True
print('abc'.upper() == 'aBc'.upper()) # True
In some non-US alphabets, converting text to upper or lower case isn't straightforward. For instance, in German, 'straße'
and strasse
are considered equivalent. However, the following code prints False
:
'straße'.lower() == 'strasse'.lower()
The str.casefold
method makes allowances for this issue and does the right thing:
'straße'.casefold() == 'strasse'.casefold()
While casefold
is only needed when working with non-US characters, it's best practice in Python to use casefold
instead of lower
or upper
, especially when comparing strings.
!=
The inequality operator, !=
, is ==
's inverse: It returns False
when ==
would return True
, and True
when ==
would return False
. It returns False
when the operands have the same type and value, True
otherwise. Other than the return value, the behaviors of ==
and !=
are identical.
print(5 != 5) # False
print(5 != 4) # True
print('abc' != 'abc') # False
print('abc' != 'aBc') # True
print(5 != '5') # True
<
and <=
The less than operator (<
) returns True
when the value of the left operand has a value that is less than the value on the right, False
otherwise. The less than or equal to operator (<=
) is similar, but it also returns True
when the values are equal; <
returns False
when the operands are equal.
print(4 < 5) # True
print(5 < 4) # False
print(5 < 5) # False
print(4 <= 5) # True
print(5 <= 4) # False
print(5 <= 5) # True
print('4' < '5') # True
print('5' < '4') # False
print('5' < '5') # False
print('42' < '402') # False
print('42' < '420') # True
print('420' < '42') # False
The examples with strings are especially tricky here! Make sure you understand them. Python compares strings character-by-character, moving from left to right. It looks for the first character that differs from its counterpart in the other string. Once it finds differing characters, it compares them to determine the relationship.
If both strings are equal up to the shorter string's length, as in the last two examples, the shorter one is considered less than the longer one.
>
and >=
The greater than operator (>
) returns True
when the value of the left operand has a value that is greater than the value on the right, False
otherwise. The greater than or equal to operator (>=
) is similar, but it also returns True
when the values are equal; >
returns False
when the operands are equal.
print(4 > 5) # False
print(5 > 4) # True
print(5 > 5) # False
print(4 >= 5) # False
print(5 >= 4) # True
print(5 >= 5) # True
print('4' > '5') # False
print('5' > '4') # True
print('5' > '5') # False
print('42' > '402') # True
print('42' > '420') # False
print('420' > '42') # True
As with <
and <=
, you can compare strings with the >
and >=
operators; the rules are similar.
You're beginning to get a decent grasp of basic conditional flow. Let's take a few minutes to see how we can combine conditions to create more complex scenarios. The not
, and
, and or
logical operators provide the ability to combine conditions:
not
The not operator returns True
when its operand is False
and returns False
when the operand is True
. That is, it negates its operand.
print(not True) # False
print(not False) # True
print(not(4 == 4)) # False
print(not(4 != 4)) # True
In these examples, Python first evaluates the expression on the right, then applies not
to the result, thus negating it. For instance, we know that 4 == 4
is True
, so not(4 == 4)
is False
.
You can omit the parentheses in the last two examples. However, we don't recommend it. Operator precedence issues may occur if you let Python decide which operator to evaluate first. Use parentheses if you have anything more complex than a single identifier or literal to the right of not
.
Unlike most operators, not
takes a single operand; it appears to the operator's right. Operators that take only one operand are called unary operators. Operators that take two operands are binary operators, though you'll rarely hear that term.
and
and or
The and operator returns True
when both operands are True
. It returns False
when either operand is False
. The or operator returns True
when either operand is True
and False
when both operands are False
.
The following truth table shows how True
and False
interact with the and
and or
operators. You should memorize this table:
A | B | A and B | A or B |
---|---|---|---|
True |
True |
True |
True |
True |
False |
False |
True |
False |
True |
False |
True |
False |
False |
False |
False |
For completeness, let's see a few examples:
print((4 == 4) and (7 == 7)) # True
print((4 == 4) and (7 == 3)) # False
print((4 == 9) and (7 == 7)) # False
print((4 == 9) and (7 == 3)) # False
print((4 == 4) or (7 == 7)) # True
print((4 == 4) or (7 == 3)) # True
print((4 == 9) or (7 == 7)) # True
print((4 == 9) or (7 == 3)) # False
As with not
, parentheses aren't always needed. However, the same "precedence" issues may occur. Always use parentheses if the corresponding operand is not a literal or identifier.
The and
and or
operators use a mechanism called short circuit evaluation to evaluate their operands. Consider these two expressions:
is_red(item) and is_portable(item)
is_green(item) or has_wheels(item)
The first expression returns True
when item
is red and portable. If either condition is False
, then the overall result must be False
. Thus, if the program determines that item
is not red, it doesn't have to determine whether it is also portable. Python short-circuits the rest of the expression by terminating evaluation if it determines that item
isn't red. It doesn't need to call is_portable()
since it already knows the expression must be False
.
Similarly, the second expression returns True
when item
is either green or has wheels. When either condition is True
, the overall result must be True
. Thus, if the program determines that item
is green, it doesn't have to decide whether it has wheels. Again, Python short-circuits the entire expression once it knows that item
is green; the expression must be True
.
Many languages can evaluate objects and values as either truthy or falsy. Python is no slouch here; it can too. It can evaluate every object's truthiness. Note that these terms are not synonymous with True
, False
, and Boolean. In addition, truthy and falsy are not actual objects or values. Instead, they are terms that describe how specific objects behave in a Boolean context.
Truthiness arises in conditional expressions, such as if
and while
statements. Conditional expressions don't need to produce Boolean values. Instead, Python only needs to determine their truthiness. In an if
statement, a conditional expression that evaluates as truthy causes the if
block to execute. The else
or elif
block runs when the expression evaluates as falsy.
Since conditionals only care about truthiness, we can use any expression as the condition. The expression will always be evaluated as truthy or falsy.
So, which values are truthy? Which are falsy? The built-in falsy values are as follows:
False
, None
0
values (integers, floats, complex)
''
[]
, ()
, {}
, set()
, frozenset()
, and range(0)
Okay, now that we know what's falsy, what's truthy? Everything else.
Enough yammering. Let's see some examples:
value = 5 # 5 is truthy
if value:
print(f'{value} is truthy')
else:
print(f'{value} is falsy')
value = 0 # 0 is falsy
if value:
print(f'{value} is truthy')
else:
print(f'{value} is falsy')
The first example prints 5 is truthy
while the second prints 0 is falsy
. This works since Python evaluates 5
as truthy and 0
as falsy.
You may sometimes see articles that speak of true and false values; this even happens in the Python documentation. The authors should probably talk of truthy and falsy evaluations, instead, not true and false. At Launch School, we want you to use truthy and falsy when speaking of truthiness, True
and False
when talking of booleans, and true and false when discussing truths and falsehoods.
You may have noticed how we took care to say things like evaluates as truthy. For brevity, you can simply describe expressions as either truthy or falsy. The "evaluates as" terminology is unnecessary.
You may recall that the and
and or
logical operators cause short-circuit evaluation. You may not realize that the logical operators don't always return True
or False
. Both operators care only about the truthiness of their operands. The final value returned by an expression using and
and or
is the value of the final sub-expression evaluated by Python:
print(3 and 'foo') # last evaluated op: 'foo'
print('foo' and 3) # last evaluated op: 3
print(0 and 'foo') # last evaluated op: 0
print('foo' and 0) # last evaluated op: 0
print(3 or 'foo') # last evaluated op: 3
print('foo' or 3) # last evaluated op: 'foo'
print(0 or 'foo') # last evaluated op: 'foo'
print('foo' or 0) # last evaluated op: 'foo'
print('' or 0) # last evaluated op: 0
print(None or []) # last evaluated op: []
Suppose you have a logical expression that returns a non-Boolean object instead of a Boolean:
foo = None
bar = 'qux'
is_ok = foo or bar
In this code, is_ok
gets set to the truthy value, 'qux'
. In most cases, you can use 'qux'
as though it were a Boolean True
value. However, using a string as a Boolean isn't always the best way to write your code. It may even look like a mistake to another programmer trying to track down a bug. In some strange cases, it may even be a mistake.
You can readily address this with an if
/else
statement:
if foo or bar:
is_ok = True
else:
is_ok = False
This snippet sets is_ok
to either True
or False
based on the truthiness of foo or bar
. However, it is wordy. Many Python programmers would write this more concisely as:
is_ok = bool(foo or bar)
As discussed in an earlier chapter, Python has precedence rules for evaluating expressions that use multiple operators and sub-expressions. The following list shows the precedence of the comparison operators from highest (top) to lowest (bottom).
==
, !=
, <=
, <
, >
, >=
- Comparison
not
- Logical NOT
and
- Logical AND
or
- Logical OR
Thus, if you have an expression like not x == y
, you can know that x == y
is evaluated first, then not
is applied to the overall result. That is, not x == y
is equivalent to not(x == y)
.
Things get really confusing when combining and
and or
in an expression. Even though and
has higher precedence than or
, the fact that both are short-circuiting operators adds a whole lot of complexity. For example, can you determine what the following code prints?
print(1 or 2 and 3)
print(0 or 2 and 3)
print(1 or 0 and 3)
print(1 or 2 and 0)
print(0 or 0 and 3)
print(0 or 2 and 0)
print(1 or 0 and 0)
print(0 or 0 and 0)
print(1 and 2 or 3)
print(0 and 2 or 3)
print(1 and 0 or 3)
print(1 and 2 or 0)
print(0 and 0 or 3)
print(0 and 2 or 0)
print(1 and 0 or 0)
print(0 and 0 or 0)
Go ahead and guess what will be output, then try running the code to see the results. In all likelihood, you will guess incorrectly at least once.
We're not going to try to explain what's happening with this code. While you might encounter some code like this in the future, the mixed and
/or
code will likely only be a small part of your problems.
In short: do not write code like this! If you must mix and
and or
, use parentheses to control how the code gets written. For instance, compare these two lines of code:
print((a and b) or (c and d))
print(a and b or c and d)
The first line, while complex, is easier to understand than the second.
To repeat, avoid mixing and
and or
in a single expression unless you use parentheses to control the order of evaluation.
The last conditional flow structure we want to discuss is the match
/case
statement (or, more concisely, the match
statement). A match
statement, in it's most basic form, is similar to an if
statement but has a different interface. It compares a single value against multiple values, whereas if
can test multiple expressions with any condition.
The Python match
statement was introduced in Python 3.10. You won't be able to use it in earlier versions, such as the 3.9 version provided by Cloud9 with the Amazon Linux 2023 operating system. However, it's straightforward to rewrite the match
statements we will use as if
statements.
match
statements use the reserved words match
and case
. It's often easier to show rather than tell, and that's certainly the case with the match
statement. First, create a file named match.py
with this content:
value = 5
match value:
case 5:
print('value is 5')
case 6:
print('value is 6')
case _: # default case
print('value is neither 5 nor 6')
# value is 5
This example is functionally identical to the following if/else
statement:
value = 5
if value == 5:
print('value is 5')
elif value == 6:
print('value is 6')
else:
print('value is neither 5 nor 6')
# value is 5
You can see how similar they are, but you can also see how they differ. The match
statement evaluates the expression value
, compares its value to the value in each case
, and executes the block associated with the first matching case
. In this example, the value of the expression is 5
; thus, the program executes the statements associated with case 5
. The statements in the case _
block run when the expression doesn't match any other case
blocks. It acts like the final else
in an if
statement and must be the last case
block in the match
statement.
If you want to match multiple values in a case, you can do so by using the |
character to separate item values:
value = 5
match value:
case 1 | 2 | 3 | 4:
print('value is < 5')
case 5 | 6:
print('value is 5 or 6')
case _: # default case
print('value is not 1, 2, 3, 4, 5, or 6')
# value is 5 or 6
There are plenty of uses for match
statements. They also have "pattern matching" abilities (which are beyond the scope of this book). They're potent tools in Python. If you're uncomfortable with them, play with the ones we presented above and watch how they respond to your changes. Test their boundaries and learn their capabilities. Curiosity will serve you well in your journey towards mastering Python. There is much to discover!
We wanted to briefly show you something we call ternary expressions. The official name is conditional expression, but that's confusing since something like a == b
is also a conditional expression. It is also sometimes called a ternary operator. However, it's not an operator.
A ternary expression is a concise way to choose between two values based on some condition. They are often used as an expression on the right side of an assignment, as function arguments, and as function return values.
Ternary expressions have the following structure:
value1 if condition else value2
Python first evaluates the condition
. If it's truthy, the expression returns value1
. Otherwise, it returns value2
. Note that only one of value1
and value2
will be evaluated.
Consider the following code:
if shape.sides() == 3:
print("Triangle")
else:
print("Square")
That's easy enough to understand, but it is a bit wordy. We can eliminate the wordiness at the sacrifice of a little clarity:
print("Triangle" if shape.sides() == 3 else "Square")
Ternaries work particularly well when you need a way to handle missing or invalid data in output:
print('N/A' if value == None else value)
Should you use ternary expressions in your code? We recommend using them only when it doesn't sacrifice too much clarity. Don't use them as a substitute for every 4-line if
/else
statement or as a way to save keystrokes: they work best as expressions.
Remember that many Python programmers dislike ternary expressions since they are hard to read. If you decide that you like ternary expressions, that's fine. However, use them judiciously. In particular, ternaries should almost always be extremely simple and fit entirely on one 79-column line of code.
This chapter covered conditionals, comparisons, logical operators, and truthiness to control the flow of code execution. These are fundamental tools you'll need to become a Python developer.
To what values do the following expressions evaluate?
False or (True and False)
True or (1 + 2)
(1 + 2) or True
True and (1 + 2)
False and (1 + 2)
(1 + 2) and True
(32 * 4) >= 129
False != (not True)
True == 4
False == (847 == '847')
False or (True and False) # False
True or (1 + 2) # True
(1 + 2) or True # 3
True and (1 + 2) # 3
False and (1 + 2) # False
(1 + 2) and True # True
(32 * 4) >= 129 # False
False != (not True) # False
True == 4 # False
False == (847 == '847') # True
Video Walkthrough
Write a function, even_or_odd
, that determines whether its argument is an even or odd number. If it's even, the function should print 'even'
; otherwise, it should print 'odd'
. Assume the argument is always an integer.
A number is even if you can divide it by two with no remainder. For instance, 4
is even since 4
divided by 2
has no remainder. Conversely, 3
is odd since 3
divided by 2
has a remainder of 1
.
To determine the remainder, you can use the %
modulo operator shown in The Basics chapter.
def even_or_odd(number):
if number % 2 == 0:
print('even')
else:
print('odd')
def even_or_odd(number):
print('even' if number % 2 == 0 else 'odd')
The solutions use the modulo operator (%) to determine whether the number is even. If the result of number % 2
is 0
, the number is even.
The second solution shows the equivalent solution using a ternary expression. The author isn't sure whether this is more readable than the first solution.
Video Walkthrough
Without running the following code, what does it print? Why?
def bar_code_scanner(serial):
match serial:
case '123':
print('Product1')
case '113':
print('Product2')
case '142':
print('Product3')
case _:
print('Product not found!')
bar_code_scanner('113')
bar_code_scanner(142)
The output is:
Product2
Product not found!
The first call to bar_code_scanner
prints Product2
since the first case
that matches '113'
is the one on line 5. The second call prints Product not found!
since the numeric value 142 doesn't match any of the case
statements with string arguments. Instead, it matches the _
"default" case
.
Video Walkthrough
Refactor this statement to use a regular if
statement instead.
return ('bar' if foo() else qux())
if foo():
return 'bar'
else:
return qux()
Ternaries are most useful when both result values are given by simple expressions; anything more complicated than calling a function or accessing a variable or literal value can lead to unreadable code. Our original code is an excellent example of using the ternary expression; the refactoring merely demonstrates how the ternary works.
Video Walkthrough
What does this code output, and why?
def is_list_empty(my_list):
if my_list:
print('Not Empty')
else:
print('Empty')
is_list_empty([])
The output is Empty
since an empty list like []
is falsy. As a result, my_list
on line 2 is falsy, so the else
block runs instead of the if
block.
Video Walkthrough
Write a function that takes a string as an argument and returns an all-caps version of the string when the string is longer than 10 characters. Otherwise, it should return the original string. Example: change 'hello world'
to 'HELLO WORLD'
, but don't change 'goodbye'
.
def caps_long(string):
if len(string) > 10:
return string.upper()
else:
return string
print(caps_long("Sue Smith")) # Sue Smith
print(caps_long("Sue Roberts")) # SUE ROBERTS
print(caps_long("Joe Shea")) # Joe Shea
print(caps_long("Joe Stevens")) # JOE STEVENS
Video Walkthrough
Write a function that takes a single integer argument and prints a message that describes whether:
number_range(0) # 0 is between 0 and 50
number_range(25) # 25 is between 0 and 50
number_range(50) # 50 is between 0 and 50
number_range(75) # 75 is between 51 and 100
number_range(100) # 100 is between 51 and 100
number_range(101) # 101 is greater than 100
number_range(-1) # -1 is less than 0
def number_range(number):
if number < 0:
print(f'{number} is less than 0')
elif number <= 50:
print(f'{number} is between 0 and 50')
elif number <= 100:
print(f'{number} is between 51 and 100')
else:
print(f'{number} is greater than 100')
Video Walkthrough