8. If/Else

Activity

Using only what we have learned so far, try to write a function smush(a_number) that checks if the number is positive or negative. If a_number is positive, the function will return half the value of a_number. If the number is not positive, simply return a_number.

You may quickly discover that you can’t quite get there yet. Think about why.

8.1. Conditional Expressions

  • Booleans were discussed in the previous topic

    • We discussed them as a type

    • How to use Boolean operators

    • How to use comparison operators

  • But Boolean expressions are helpful for more than just evaluating to True/False values

  • Consider smush from the above activity

  • We know how to ask if a_number is positive

    • a_number > 0

  • The key now is to tell Python that, if some condition is True, do something

    • if some_condition:

    • The code to be run if the condition is True is

 1def smush(a_number: float) -> float:
 2    """
 3    Returns half the value of the parameter a_number if the value is positive,
 4    otherwise, return the value of a_number.
 5
 6    :param a_number: Some arbitrary number.
 7    :return: Half of a_number when it is positive, a_number when not positive.
 8    """
 9    return_value = a_number
10    if a_number > 0:
11        return_value = a_number / 2
12    return return_value
13
14
15# Tests for smush
16assert 5 == smush(10)
17assert -10 == smush(-10)
18assert 0 == smush(0)
  • In the above example we made use of an if statement

  • When the evaluated Boolean value after the if is True, the indented code is executed, otherwise the indented code block is ignored

  • If we follow the code within the function when a_number is 10, the execution is as follows

    • Assign the value of a_number (10) to return_value

    • Evaluate a_number > 0

      • 10 > 0

      • True

    • Since the expression evaluated to True, the indented code is run

    • Divide return_value by 2 and assign it back into return_value

    • Return the value stored in return_value (5)

  • Similarly, if we follow the code when a_number is -10, the execution is as follows

    • Assign the value of a_number (-10) to return_value

    • Evaluate a_number > 0

      • -10 > 0

      • False

    • Since the expression evaluated to False, the indented code is skipped

    • Return the value stored in return_value (-10)

Activity

Write a function is_negative(a_number) that returns True if a_number is negative and False otherwise.

8.2. Compound Conditions

  • When we make use of an if statement, the value being checked needs to ultimately be evaluated to a Boolean

  • This means that we can make use of more complex compound Boolean expressions

    • Comparison operators

    • Arithmetic operators

    • Boolean operators

 1def three_five_divisible(a_number: float) -> str:
 2    """
 3    Checks if a number is divisible by both three and five. If it is, return
 4    a string "It is!", otherwise "Nope".
 5
 6    :param a_number: Some arbitrary number.
 7    :return: String indicating if the number is divisible by three and five
 8    """
 9    if a_number % 3 == 0 and a_number % 5 == 0:
10        return "It is!"
11    return "Nope"
12
13
14# Tests for three_five_divisible
15assert "It is!" == three_five_divisible(0)
16assert "It is!" == three_five_divisible(15)
17assert "It is!" == three_five_divisible(-30)
18assert "Nope" == three_five_divisible(3)    # Divisible by 3 but not 5
19assert "Nope" == three_five_divisible(-50)  # Divisible by 5 but not 3
20assert "Nope" == three_five_divisible(1)    # Divisible by neither

Note

The modulo operator % (often called just “mod”) effectively does division and returns the remainder. For example, 10 % 3 is 1 since 10/3 is 3 remainder 1.

In the three_five_divisible example, we are checking if the remainder of the division is 0, which would mean that the value can be evenly divided.

Another common use of % is checking if a value is even or not — x % 2 is 0 when x is even since it would mean that x can be evenly divided by 2.

  • The above function three_five_divisible needs to check if a number is divisible by 3 and 5

  • This means that there are two conditions we need to check for being True

  • If we follow the code within the function when a_number is 15, the execution is as follows

    • Evaluate a_number % 3 == 0 and a_number % 5 == 0

      • 15 % 3 == 0 and 15 % 5 == 0

      • 0 == 0 and 0 == 0

      • True and True

      • True

    • Since the expression evaluated to True, the indented code is run

    • Return "It is!", function ends

  • If we follow the code within the function when a_number is 9, the execution is as follows

    • Evaluate a_number % 3 == 0 and a_number % 5 == 0

      • 9 % 3 == 0 and 9 % 5 == 0

      • 0 == 0 and 4 == 0

      • True and False

      • False

    • Since the expression evaluated to False, the indented code is skipped

    • Return "Nope"

8.3. Alternative Execution

  • This pattern is very common

1if x > 10:
2    do_something()
3if not x > 10:
4    do_something_else()
  • When we have an either/or situation we make use of else

1if x > 10:
2    do_something()
3else:
4    do_something_else()
  • The two examples above will effectively do the same thing, but the second is nicer

    • Less code to write

    • Intuitive and easy to read/understand

    • Eliminate potential bugs

Activity

Write a function called hail that takes an integer as an argument. If the integer is even, return the value of that integer divided by 2. If it is odd, return the value multiplied by 3 and with one added. In other words, given a number \(n\), return \(n/2\) when it is even and \(3n + 1\) when it is odd. Hint: Don’t forget about %.

This is actually some neat math stuff. Isn’t it cool that we’re writing a Python function that’s doing exactly what the math is saying?

Note

If we revisit smush, we can rewrite the function in a few different ways that are all correct.

 1def smush_version_2(a_number: float) -> float:
 2    """
 3    Returns half the value of the parameter a_number if the value is positive,
 4    otherwise, return the value of a_number.
 5
 6    :param a_number: Some arbitrary number.
 7    :return: Half of a_number when it is positive, a_number when not positive.
 8    """
 9    if a_number > 0:
10        return_value = a_number / 2
11    else:
12        return_value = a_number
13    return return_value

In smush_version_2, an else is used and the function has only one return. The use of the else here is not required (as seen in the original smush), but the use of else in this situation may make the function a little clearer. Additionally, some programmers prefer having their functions have only one return, but this is by no means more correct.

 1def smush_version_3(a_number: float) -> float:
 2    """
 3    Returns half the value of the parameter a_number if the value is positive,
 4    otherwise, return the value of a_number.
 5
 6    :param a_number: Some arbitrary number.
 7    :return: Half of a_number when it is positive, a_number when not positive.
 8    """
 9    if a_number > 0:
10        return a_number / 2
11    else:
12        return a_number

Another possibility is smush_version_3. You will notice how similar it is to version 2, but here we use two returns in the if and else blocks. Again, this is not more correct and it is only shown here to demonstrate how the same functionality can be implemented differently.

8.4. Exclusive Alternatives

  • Sometimes we need to check various conditions and if/else isn’t good enough

  • For example, what if I want a function to take a percentage grade and return a letter grade

 1def letter_grade_broken(percent_grade: float) -> str:
 2    """
 3    Calculate the letter grade associated with the provided percent grade.
 4
 5    :param percent_grade: A grade as a percent
 6    :return: Letter grade for the provided percentage
 7    """
 8    if percent_grade >= 90:
 9        letter_grade = "A+"
10    if percent_grade >= 80:
11        letter_grade = "A"
12    if percent_grade >= 70:
13        letter_grade = "B"
14    if percent_grade >= 60:
15        letter_grade = "C"
16    if percent_grade >= 50:
17        letter_grade = "D"
18    else:
19        letter_grade = "F"
20    return letter_grade
  • The above letter_grade_broken has a subtle problem

  • If we run assert "A+" == letter_grade_broken(99)

    • letter_grade_broken(99) would actually return "D"

  • To understand the problem, follow the execution carefully

    • Call letter_grade_broken(99)

    • percent_grade is assigned the value 99

    • Check if percent_grade >= 90

      • percent_grade >= 90

      • 99 >= 90

      • True

    • Since the expression is evaluated to True, the indented code is run

    • Assign letter_grade the value "A+"

    • The execution continues

    • Check if percent_grade >= 80

      • percent_grade >= 80

      • 99 >= 80

      • True

    • Since the expression is evaluated to True, the indented code is run

    • Updates letter_grade the value "A"

    • This continues for each if

  • The trouble here is that we really only want one of these if code blocks to run

    • We want them to be mutually exclusive alternatives

  • There are a few ways one could fix this

    • Have a return in each indented block since that would stop execution of the function once a return is reached

    • Reverse the order of the ifs

    • Check upper and lower bounds (e.g. percent_grade >= 80 and percent_grade < 90)

  • The cleaner way to address this is with elifs

    • Can be read as else, if…

  • These allow us to have at most one of the code blocks in the chain of conditions to run

    • As soon as one of the ifs is true, all other ifs are skipped

    • The program continues after the if/elif/else chain

  • When using elifs, it’s good practice to end with a final else

 1def letter_grade(percent_grade: float) -> str:
 2    """
 3    Calculate the letter grade associated with the provided percent grade.
 4
 5    :param percent_grade: A grade as a percent
 6    :return: Letter grade for the provided percentage
 7    """
 8    if percent_grade >= 90:
 9        letter_grade = "A+"
10    elif percent_grade >= 80:
11        letter_grade = "A"
12    elif percent_grade >= 70:
13        letter_grade = "B"
14    elif percent_grade >= 60:
15        letter_grade = "C"
16    elif percent_grade >= 50:
17        letter_grade = "D"
18    else:
19        letter_grade = "F"
20    return letter_grade

8.5. Nesting Conditionals

../../_images/nesting_dolls.png
  • You can “nest” conditionals inside other conditionals

 1# Find quadrant with 'nested If's
 2if x > 0:
 3    if y > 0:
 4        print("First Quadrant")
 5    else:
 6        print("Fourth Quadrant")
 7else:
 8    if y > 0:
 9        print("Second Quadrant")
10    else:
11        print("Third Quadrant")
  • For simplicity, ignore point \((0,0)\) being in the third quadrant

  • In the above example, we could have done it without nesting by using ands

  • But some may find the nested version of the code more intuitive and readable

1# Find quadrant with 'and's
2if x > 0 and y > 0:
3    print("First Quadrant")
4elif x > 0 and y < 0:
5    print("Fourth Quadrant")
6elif x < 0 and y > 0:
7    print("Second Quadrant")
8else:
9    print("Third Quadrant")

Note

If you find yourself writing long and complex conditional expressions, it’s worth stepping back — there’s usually a cleaner way. Even with a long list of conditions needing to be checked, there are ways to make them more manageable and easier to follow.

  1. Use nesting, like in the above example

  2. Save sub-expression results

  3. Extract condition into its own function

8.6. For Next Topic