Chapter 3 Logic and Conditionals
Programming involves writing instructions for a computer to execute. However, what allows computer programs to be most useful is when they are able to decide which instructions instructions to execute based on a particular situation. This is referred to as code branching, and is used to shape the flow of control of the computer code. In this chapter, you will learn how to utilize conditional statements in order to include control flow in your Python scripts.
In addition to the basic data types
str, Python supports a logical data type called a Boolean (class
bool). A boolean represents “yes-or-no” data, and can be exactly one of two values:
False (note the capitalization). Importantly, these are not the Strings
"False"; boolean values are a different type!
type(True) # <class 'bool'> type("True") # <class 'str'> type(true) # NameError: name 'true' is not defined # e.g., no variable called `true`!
Note that boolean variables should be named as statements of truth. Use words such as
has in the variable name:
is_early = True is_sleeping = False has_work = True needs_coffee = True
Naming boolean variables like statements will help make it clear that their value is a boolean, and make it easier to read and understand your logic.
3.1.1 Relational Operators
Boolean values are most commonly the result of applying a relational operator (also called a comparison operator) to some other data type. Comparison operators are used to compare values and include:
< (less than),
> (greater than),
<= (less-than-or-equal, written as read),
>= (greater-than-or-equal, written as read),
== (equal), and
x = 3 y = 3.15 # compare numbers x > y # returns logical value False ("x is bigger than y" is a False statement) y != x # returns logical value True ("y is not-equal to x" is a True statement) # compare x to pi (built-in variable) y == math.pi # returns logical value False # compare strings (based on alphabetical ordering) "cat" > "dog" # returns False # Note that when comparing strings, upper-case letters are always considered # "earlier" than lower-case letters "Z" < "a" # returns True
IMPORTANT DO NOT SKIP
== (two equals signs) is a comparison operator, but
= (one equals sign) is the assignment operator! This a really common mistake and one of the hardest ones to track down.
= is assignment;
== is comparison.
In computer programming, we default to “strict inequalities” (
>) by default. So if someone says “more than”, you can assume they mean
> and not
3.1.2 Boolean Operators
In addition, boolean values support their own operators (called logical operators or boolean operators). These operators are applied to boolean values and produce boolean values, and allow you to make more complex boolean expressions:
Trueif both of the operands are
Trueif either of the operands are
not(negation) is a unary operator that produces
Trueif the operand is
x = 3.1 y = 3.2 # Assign bool values to variables x_less_than_pi = x < math.pi # True y_less_than_pi = y < math.pi # False # Boolean operators x_less_than_pi and y_less_than_pi # False x_less_than_pi or y_less_than_pi # True # This works because Python is amazing x < math.pi < y # True # Assign str value to a variable pet = "dog" # It is NOT the case that pet is "cat" not pet == "cat" # True # pet is "cat" OR "dog" pet == "cat" or pet == "dog" # True
In the last example, there is an implicit order of operations being followed: relational operators are evaluated before boolean operators. This means that the last line is being interpreted as
(pet == "cat") or (pet == "dog").
This also means that you can’t use some “plain English” construction of expressions:
# This does not work! pet == "cat" or "dog" # returns "dog"
In this case, the expression is evaluated as
(pet == "cat") or "dog". And since
pet == "cat" is
False, then the statement is read as
False == "dog", which will return the second operand (see “short-circuiting” below). In short, if you want to check if a variable has one of a set of values, you need to compare each individually (
pet == "cat" or pet == "dog").
Because boolean expressions produce more booleans, it is possible to combine these into complex logical expressions:
# given two booleans P and Q is_raining = True is_sunny = False is_raining and not is_sunny # True; evaluated as `is_raining and (not is_sunny)` not is_raining and is_sunny # False not (is_raining and is_sunny) # True (not is_raining) or (not is_sunny) # True
The last two expressions in the above example are equivalent logical statements for any two boolean values, what is known as De Morgan’s Laws. Indeed, many logical statements can be written in multiple equivalent ways.
- Use parentheses to explicitly enforce the order of operations. Values inside the parentheses will be evaluates first, and you can work your way out to determine the resulting value.
Finally, note that when using an
and operator, Python will short-circuit the second operand and never evaluate it if the first is found to be
False (after all, if the first operand isn’t
True there is no way for the entire
and expression to be!)
x = 2 y = 0 x == 2 and x/y > 1 # ZeroDivisionError: division by zero x == 3 and x/y > 1 # no error (x == 3 is False, # so short-circuits without dividing by 0) # Use a "guardian expression" (make sure y is not 0) to avoid any errors y != 0 and x/y > 1 # no error (short-circuited)
The reason this works is because when the Python interpreter reads the expression
P and Q, it produces
P otherwise. After all if
True, then the overall truth of the expression is dependent entirely on
Q (and thus that can just be returned). Similarly, if
False, then the whole statement is False (which is the value of
P, so just return that!). See here for a more detailed example.
3.2 Conditional Statements
One of the primary uses of Boolean values (and the boolean expressions that produce them) is to control the flow of execution in a program (e.g., what lines of code get run in what order). While we can use functions are able to organization instructions, we can also have our program decide which set of instructions to execute based on a set of conditions. These decisions are specified using conditional statements.
In an abstract sense, an conditional statement is saying:
IF something is true do some lines of code OTHERWISE do some other lines of code
In Python, we write these conditional statements using the keywords
else and the following syntax:
if condition: # lines of code to run if condition is True else: # lines of code to run if condition is False
condition can be any boolean value (or any expression that evaluates to a boolean value). Both the
if statement and
else clause are followed by a colon
: and a block, which is a set of indented statements to run (similar to the blocks used in functions). It is also possible to omit the
else statement and its block if there are no instructions to run in the “otherwise” situation:
porridge_temp = 115 # temperature in degrees F if porridge_temp > 120: print("This porridge is too hot!") else: print("This porridge is NOT too hot!") too_cold = porridge_temp < 70 # a boolean variable if too_cold: print("This porridge is too cold!") # This line is outside the block, so is not part of the conditional # (it will always be executed) print("Time for a nap!")
Blocks can themselves contain nested conditional statements, allowing for more complex decision making. Nested
if statements are indented twice (8 spaces or 2 tabs). There is no limit to how many “levels” you can nest; just increase the indentation each time.
# nesting example if outside_temperature < 60: print("Wear a jacket") else: if outside_temperature > 90: print("Wear sunscreen") else: if outside_temperature == 72: print("Perfect weather!") else: print("Wear a t-shirt")
Note that this form is nesting is also how you can use conditionals inside of functions:
def flip(coin_is_heads): if coin_is_heads: print("Heads you win!") else: print("Tails you lose.")
If you consider the above nesting example’s logic carefully, you’ll notice that many of the “branches” are mutually exclusive: that is, the code will choose only 1 of 4 different clothing suggestions to print. This can be written in a cleaner format by using an
elif (“else if”) clause:
if outside_temperature < 60: print("Wear a jacket") elif outside_temperature > 90: print("Wear sunscreen") elif outside_temperature == 72: print("Perfect weather!") else: print("Wear a t-shirt")
In this situation, the Python interpreter will perform the following logic:
- It first checks the
ifstatement’s condition. If that is
True, then that branch is executed and the rest of the clauses are skipped.
- It then checks each
elifclause’s condition in order. If one of them is
True, then that branch is executed and the rest of the clauses are skipped.
- If none of the
True, then (and only then!) the
elseblock is executed.
This ordering is important, particularly if the conditions are not in fact mutually exclusive:
if porridge_temp < 120: print("This porridge is not too hot!") elif porridge_temp < 70: # unreachable statement! the condition will never be both checked and True print("This porridge is too cold!") # contrast with: if porridge_temp < 120: print("This porridge is not too hot!") if porridge_temp < 70: # a second if statement, unrelated to the first print("This porridge is too cold!") # both print statements will execute for `porridge_temp = 50`
See also the resources at the end of the chapter for explanations with logical diagrams and flowcharts.
3.2.1 Designing Conditions
Relational operators all have logical opposites (e.g.,
== is the opposite of
<= is the opposite of
>), and boolean expressions can include negation. This means that there are many different ways to write conditional statements that are logically equivalent:
# these two statements are equivalent if x > 3: print("big X!") if 3 < x: print("big X!")
The second example is known as a Yoda condition; in Python you should use the former version (variable parts of the condition on the left).
Thus you should follow the below guidelines when writing conditionals. These will let you produce more “idiomatic” code which is cleaner and easier to read, as well as less likely to cause errors.
Avoid checks for mutually exclusive conditions with an
elif. Use the
# Do not do this! if temperature < 50: print("It is cold") elif temperature >= 50: # unnecessary condition print("It is not cold") # Do this instead! if temperature < 50: print("It is cold") else: print("It is not cold")
Avoid creating redundant boolean expressions by comparing boolean values to
True. Instead, use an effectively named variable.
# Do not do this! if is_raining == True: # unnecessary comparison print("It is raining!") # Do this instead! if is_raining: # condition is already a boolean! print("It is raining!")
Note that this gets trickier when trying to check for
Falsevalues. Consider the following equivalent conditions:
# I believe this is the cleanest option, as it reads closet to English if not is_raining: print("It is not raining!") # This is an acceptable equivalent, but prefer the first option if is_raining == False print("It is not raining!") # Use one of the above options instead if is_raining != True print("It is not raining!") # This can be confusing unless your logic is explicitly based around the # ABSENCE of some condition if is_not_raining: print("It is not raining!")
Overall, try to develop the simplest, most straightforward conditions you can. This will make sure that you are able to think clearly about the program’s control flow, and help to clarify your own thinking about the program’s logic.
3.3 Determining Module or Script
As discussed in Chapter 5, it is possible to define Python variables and functions in
.py files, which can be run as stand-alone scripts. However, these files can also be imported as modules, allowing you to access their variables and functions from another script file—similar to how you imported the
math module. Thus all Python scripts have two “modes”: they can be used as executable scripts (run with the
python command at the command line), or they can be imported as modules (libraries of variables and functions, using the
import keyword in the script).
.py script is run as an executable top-level script, you often want to perform special instructions (e.g., call specific functions, prompt for user input, etc). You can use a special environmental variable called
__name__ to determine whether a script is the “main” script that is being run, and
if so execute those instructions:
if __name__ == "__main__": # execute only if run as a script print("This is run as a script, not a module!")
__ in the variable names and values are a special naming convention used to indicate to the human that these values are internal to the Python language and so have special meaning. They are otherwise normal variables names.
It is best practice to define all the functions your script will utilize, and then use the “is run as a script” check to determine whether or not to execute those functions—this will allow you to easily reuse you code in a different task without adding any side effects!