Decisions

Tasks

Learning Outcomes

By the end of this lesson, you should be able to:

  1. Identify conditional statements in Python code
  2. Integrate logical expressions in conditional statements
  3. Write simple conditional statements
  4. Write a sequence of conditional statements using if-elif-else structure
  5. Write nested conditional statements

Key Terms/Concepts

Introduction

In the previous three lessons, you learned how to write sequential Python statements. Sequential statements refer to sequence of commands that are executed one after the other in the given order. In Lesson 4, you learned about functions which execute a named block of code when a function call is invoked. This lesson discusses conditional statements.

Conditional statements are Python commands that enable the execution of blocks of code based on the satisfaction of specific condition(s). Such commands exist in almost all structural and procedural programming languages. This conditional execution of code blocks set up multiple mutually exclusive execution paths in program code.

We start simple by introducing the if statement. Then, you will gradually expand the if statement into if-else statements, then elif statements, and finally nested if-else statements. In addition, we look deeper into simple and compound logical statements. 

if Statements

The simplest conditional statement is the if statement. As the name suggests, a block of code is executed 'only if' a specific condition is satisfied. The Python syntax for if-statements is:

The if Statement

if <logical  expression>:
    <block of code>

The first line starts with the keyword if. This keyword is followed by a space, then by a logical expression, followed by a colon (:). As you may recall from Lesson 3, a logical expression is an expression that evaluates to either True or False.

The if defines the start of a code block. Like the function def line, all code that belongs to the if are indented with the same indentation.

if blocks may be used within main program code or within functions, or within other if blocks (these are nested ifs.)

Some simple if statements:

if cost > 0.00: # compares variable to float
if weight < 100: # compares variable to int
if name == "David": # compares variable to string
if first >= last: # compares variable to variable
if result == True: # compares variable to boolean
if result: # tests boolean (which has the same effect as the line above)

It is possible, but not particularly useful, to write:

if 3 > 5: # compares raw number to raw number
if True: # tests True

as these statements always evaluate to either False or True, which is pointless. Useful ifs always involve a variable.

A more common design mistake is to use a statement that theoretically could evaluate to True or False, but practically evaluates to a fixed Boolean value. An example of such statement is the following:


if exam_score > 200: 
  print('Good score')

In the context of a exam a valid score is a number between 0 and 100, the above statement always, practically speaking, evaluates to False since no valid score is ever greater than 100. Such code is redundant and should be avoided.

To understand the importance of the above discussion, I invite you to put on some special goggles and look into the mind of a successful programmer. When looking, do not be distracted by the variety of commands and complex statements in the programmer's mind. Focus on how the mind operates during coding. 

You should observe that the mind is not only focused on writing the current line of code, but it predicts how the overall program behaves with some expectation of outputs. So, the mind is coding without losing the big picture and while keeping an eye on the expected output. Such a mind is unlikely to write redundant statements.

Alas! We do not have such goggles to enjoy looking into good programmers' minds. However, we can imitate these good programmers in their good programming practices. Through some patience and diligence, you could be one of them.

The following trivial function uses an if to determine if an exam score is a pass:

A Simple Score Checker

# Constants
MINIMUM_PASS = 50

def pass_exam(score):
    """
    -------------------------------------------------------
    Determines if score is a pass.
    Use: passed = pass_exam(score)
    -------------------------------------------------------
    Parameters:
        score - numeric score (0 <= float <= 100)
    Returns:
        passed - True if score >= MINIMUM_PASS, False otherwise (bool)
    -------------------------------------------------------
    """
    passed = False

    if score >= MINIMUM_PASS:
        passed = True
    return passed

The function sets a default value for the variable passed , and that variable is changed only when the if condition is triggered.

if-else Statements

Structure of if-else Statement:

The if statement provides a mechanism to execute code when a condition is satisfied. The if-else statement provides alternate code if the condition was not satisfied.

The structure of the if-else statement is as follows:

The if - else Statement

if <logical expression>:
    <code block 1> 
else:
    <code block 2>

Observe how the else command appear on a separate line and has the same indentation as the if statement. Also, observe how code block 1 and 2 are both on the same indentation level.

Similar to the if-statement, in an if-else statement both block 1 and 2 may contain a single line or multiple lines of code. It is illegal to leave either block with no lines of code. Remember to include the colon after the keyword else. This is a common syntax error among students learning Python.

The if-else statement is like a toggle switch. You can only activate one side at a single time, not both. If the logical expression evaluates to True, code block 1 is executed and block code 2 is ignored. On the other hand, if the logical expression evaluates to False, code block 1 is ignored and code block 2 is executed.

Let us re-write the code for pass_exam using an if-else statement:

pass_exam with if-else

def pass_exam(score):

    if score >= MINIMUM_PASS:
        passed = True
    else:
        passed = False
    return passed

This solution gives the same result for the same logical reason as the previous version, it just uses an else.

Example of if-else Statement

Implement the following function:

get_parity Function Description

def get_parity(num):
    """
    -------------------------------------------------------
    Determines score is a pass.
    Use: parity = get_parity(num)
    -------------------------------------------------------
    Parameters:
        num - a numeric value (int)
    Returns:
        parity - "Even" if num is evenly divisible by 2, "Odd" otherwise (str)
    -------------------------------------------------------
    """

The function docstring tells us that the function determines the parity of a number and returns a string with value of "Even" or "Odd" as appropriate.

Before we start coding we need to think first on how we can determine if an integer is even or odd. In mathematics an integer is even if it is divisible by 2 and is odd if it is not divisible by 2. You already know this from your early school years. The relevant question here is how can we translate 'divisible by 2' into code?

Going back to mathematics, if number X is divisible by Y, then it means the remainder of dividing X by Y is 0. In Python, the modulo operator % comes to our rescue. Try writing and testing this function, then look at the solution:

Functions with Multiple if-else Statements

Programs and statements may use multiple if-else statements.

Example

Implement the following function:

classify_num Function Description

def classify_num(num, divisor):
    """
    -------------------------------------------------------
    Prints the parity of num and whether it is evenly divisible by divisor.
    Use: classify_num(num)
    -------------------------------------------------------
    Parameters:
        num - a numeric value (int)
    Returns:
        None
    -------------------------------------------------------
    """

The function finds the parity of num and whether it is divisible by divisor. The function does not return any value. Instead, the function prints one of the following messages to the console:

We have already seen how to find out if a number is even or odd. So, we can borrow that code in our solution. Let us start with:


    # check if even or odd
    if num % 2 == 0:
        print(f"{num} is even")
    else:
        print(f"{num} is odd")

The only difference to the previous solution is that we replaced the parity assignment with a print statement.

Next, we need to add another if-else statement to find out if the number is evenly divisible by divisor:


    # check against divisor
    if num % divisor == 0:
        print(f"and divisible by {divisor}")
    else:
        print(f"but not divisible by {divisor}")

The final step is to make both print statements work together to produce a single line. To do that, we need to ensure that the print statement in the first if-else block does not end with a new line. This could be done by adding end="" at the end of the print statements. The full solution is:

if-elif Statements

Structure of an elif Statement

The if-else statement supports us with a solution for scenarios that have an answer of Yes/No. Many real-life scenarios require answers beyond Yes and No. For instance, a student grade is normally assigned on a scale of A, B, C, D, and F, rather than a simple Pass/Fail scheme. To address this issue, Python has the elif statement for a multi-decision process.

The elif statement is not a stand-alone command. It is an extension of the if-else structure that you learned in the previous sections. It has the following structure:

The if - elif - else Statement

if <logical expression 1>:
    <code block 1> 
elif <logical expression 2>:
    <code block 2>
...
[elif <logical expression n-1>
    <code block n-1>]
[else:
    <code block n>]

The above structure allows for multi-decisions without a limit on the maximum number of decisions. If you want to make n decisions, you require n code blocks: one if statement, n-2 elif statements and one else statement. The code blocks within the structure are mutually exclusive: only once of the code blocks within the structure can possibly be executed. Let us walk through how the interpreter executes such a structure:

Each of these possible code block executions is called an execution path. There is only ever one execution path through an if-elif-else structure. Here is an example:

Execution Paths

It is obvious that the test values evaluated must be:

This if-elif-else covers all the possibilities.

The Fallthrough Algorithm

The following code examines a score and returns a string equivalent of the score:

The get_grade Function

def get_grade(score):

    if score <= 100 and score >= 90:
        grade = "A"
    elif score < 90 and score >= 80:
        grade = "B"
    elif score < 80 and score >= 65:
        grade = "C"
    elif score < 65 and score >= 50:
        grade = "D"
    else:
        grade = "F"
    return grade

Note that the function does not handle cases where a bad score value (such as -12 or 245) is passed as a parameter. We will examine that in the next section.

Let us trace the program if the value of score is 77. The following lines are evaluated, but the code blocks they define are skipped since none of them evaluate to True if score is 77:


    if score <= 100 and score >= 90:
        …
    elif score < 90 and score >= 80:
        …

The following code block is executed:


    elif score < 65 and score >= 50:
        grade = "D"

and the remaining elif and else blocks are skipped entirely. Again, all of the decision blocks are mutually exclusive. Only one code block within such a structure can be executed. Instead the code block:


    return grade

is executed since it is the first line of code that follows the if structure.

Fallthrough Simplification

We can shorten this code by rewriting it as a fallthrough structure.

We can take advantage of the fact that elif statements are executed sequentially. When the code execution of Listing 1 reaches line 7, you are sure that the score is in the range [0,79]. The test in line 5 is False for any value greater than 79, thus there is no way to reach line 7 if the score is too large. If that is the case, then why bother to check for a value less than 80 in line 7? That check has already been done. We can take advantage of these 'shortcuts' and rewrite the function accordingly:

The Rewritten get_grade Function

def get_grade(score):

    if score >= 90:
        grade = "A"
    elif score >= 80:
        grade = "B"
    elif score >= 65:
        grade = "C"
    elif score >= 50:
        grade = "D"
    else:
        grade = "F"
    return grade

This is called a fallthrough algorithm. The value being checked 'falls through' the various if and elif clauses until it reaches a clause that evaluates to True. Note that this only works if the logical expressions are in order. The order doesn't matter in the sense that we could look at scores from 100 down to 0 (descending), or from 0 up to 100 (ascending), but the expressions have to be in order.

Nested if Statements

So far, you have seen the if statement, the if-else statement and the if-elif-else statement. However, the discussion is not complete without introducing the concept of nested if statements.

Nested if statements mean using an if structure inside another if structure. It is like a hierarchy in which you can have multiple layers of if structures within each other.

We used the get_grade function to demonstrate a fallthrough algorithm. What the oroginal code did not do was to verify that the score parameter was a valid grade score. The following code is a re-implementation of the get_grade that uses nested if statements to check for bad parameter values:

The Improved get_grade Function

def get_grade(score):

    if score >= 0 and score <= 100:
        if score >= 90:
            grade = "A"
        elif score >= 80:
            grade = "B"
        elif score >= 65:
            grade = "C"
        elif score >= 50:
            grade = "D"
        else:
            grade = "F"
    else:
        grade = "invalid score"
    return grade

In the above solution, the first if-else structure contains a nested if-elif-else structure. The function now checks for a score that is valid, then uses a fallthrough algorithm to determine what the matching grade is.

Having unnecessary nested if structures is not a good programming practice. Below is an example of the bad practice:

The Too Complex get_grade Function

def get_grade(score):

    if score >= 0 and score <= 100:
        if score >= 90:
            grade = "A"
        else:
            if score >= 80:
                grade = "B"
            else:
                if score >= 65:
                    grade = "C"
                else:
                    if score >= 50:
                        grade = "D"
                    else:
                        grade = "F"
    else:
        grade = "invalid score"
    return grade

Although the function executes exactly like the if-elf-else version, the use of nested ifs rather than elifs makes the code harder to read and understand.

Logical Expressions

If you have got this so far, it means you have now a complete overview of conditional statements in Python. During your reading, you might be wondering why we skipped the concept of logical expressions, despite it being an important component of the conditional statements topic. The rationale goes back to the observation that some students struggle when learning about conditional statements if it is mixed with too many details about logical expressions. Therefore, we decided to move the discussion to the end when you are comfortable enough with if-else statements.

The concept of logical expressions and operators were briefly introduced in Lesson 3. The emphasis of Lesson 3, however, was on arithmetic expressions not logical expressions. We are revisiting the topic here because of its strong relevance to conditional statements.

Python, like most languages, has three main logical operators: and, or and not. These operators work on Boolean values and produce Boolean results. We will provide a separate discussion for each operator.

Logical not Operator

The simplest logical operator is the unary not operator. The operator toggles the value of a Boolean variable or literal. If it is True, it makes False, and if it is False it makes it True.

Let us see how this works in if-else statements.

Example

A museum, during the summer season, is offering eight new galleries to its visitors. Tickets sold during the summer season allow non-member guests to access galleries 2 to 6, while members can access galleries 1 as well. If a user has a golden membership, then they have exclusive access to gallery 8, in addition to the other galleries.

Implement the function deny_access(). The function asks a user what level of membership they have, then prints a statement outlining which galleries have been denied access based on their membership level. The solution:

Logical and Operator

The logical and operator returns True only if both operands are True. It evaluates to False, if either operand (or both) is False. We snuck in an example of using and with the get_grade function, as in the line:

and Sample Usage
          
def get_grade(score):

    if score >= 0 and score <= 100:
        …
      
        

Line 3 used the and operator to verify that the score parameter was within a valid range. (This could have been written with a more complex set of nested if statements, but using a logical operator can simplify code immensely.

It is interesting how logical operators allow us to write code that resembles the English language.

There is a little trick to learn about the and operator. If there are multiple operands in a logical expression that uses an and operator, then it only takes one operand to be False to force the entire expression to evaluate to False.

For example, Line 3 from above evaluates to False if any condition is False. If it turns out that score is less than zero, Python doesn't even bother to compare score to 100. We call this short-circuit evaluation.

Logical or Operator

The logical or operator returns True if any operand is True. It evaluates to False, only if both operands are False. Here is an example:

The or operator is different than the and operator in some aspects and is similar in others. Study the following table:

Logical or and Logical and Operators
A B A and B A or B
True True True True
True False False True
False True False True
False False False False

You can see that both operators produce True if both operands are True, and produce False if both operands are False, but differ in the output if operands have different values. Therefore, it is inaccurate to think of the and-or operators as opposites (complements) of each other.

Note that the short-circuit evaluation for the or operator happens if the interpreter detects any operand with the value True.

Compound Logical Expressions

Logical expressions can use a combination of logical operators in a single expression. Some of these expressions are easy to analyze, but others require some analysis to predict the output.

Study the following example:

The study of logical operators is the focus of a discipline in computer science called digital logic design. This is also the title of one of the courses which you will take in your computer science degree. If you like such analysis, you will find the course a joyful experience.

Conclusion

In Lesson 5, you have learned about conditional statements in Python. You can now read and write conditional statements using if and if-else structures. You have also learned how to expand such expressions using the if-elif-else structure. In addition, you are now more familiar with logical expressions and know how to use them in conditional statements.

In this week's assignment, you will get a chance to practice problem-solving using Python. You should be able to recognize that there are multiple approaches to solve the problems, and each approach comes with its own advantages in terms of writability and readability.

In the upcoming lesson, you will carry what you learned here and apply it to a new topic which is the while loops. The topic of while loops is a good bridge between the topic of conditional statements and iterative statements.

Check Weekly Schedule for Assessments

References

  1. Peter Wentworth, Jeffrey Elkner, Allen B. Downey and Chris Meyers. (October 2012). How to think like a Computer Scientist. Learning with Python 3: Chapter 5: Conditionals.Open Book Project. ACM50, 1 Retrieved on July 15, 2020