8. If/Else

Activity

Using only what we have learned so far, 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.

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 Booleans allow us to do a lot more than just evaluate an expression to True/False

  • 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

 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    :rtype: float
 7    :param a_number: Some arbitrary number.
 8    :return: Half of a_number when it is positive, a_number when not positive.
 9    """
10    return_value = a_number
11    if a_number > 0:
12        return_value = return_value / 2
13    return return_value
14
15
16# Tests for smush
17assert 5 == smush(10)
18assert -10 == smush(-10)
19assert 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

    • Arithmatic 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    :rtype: str
 7    :param a_number: Some arbitrary number.
 8    :return: String indicating if the number is divisible by three and five
 9    """
10    if a_number % 3 == 0 and a_number % 5 == 0:
11        return "It is!"
12    return "Nope"
13
14
15# Tests for three_five_divisible
16assert "It is!" == three_five_divisible(0)
17assert "It is!" == three_five_divisible(15)
18assert "It is!" == three_five_divisible(-30)
19assert "Nope" == three_five_divisible(3)    # Divisible by 3 but not 5
20assert "Nope" == three_five_divisible(-50)  # Divisible by 5 but not 3
21assert "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 2nd is nicer

    • Write less

    • 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    :rtype: float
 7    :param a_number: Some arbitrary number.
 8    :return: Half of a_number when it is positive, a_number when not positive.
 9    """
10    if a_number > 0:
11        return_value = a_number / 2
12    else:
13        return_value = a_number
14    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    :rtype: float
 7    :param a_number: Some arbitrary number.
 8    :return: Half of a_number when it is positive, a_number when not positive.
 9    """
10    if a_number > 0:
11        return a_number / 2
12    else:
13        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    :rtype: str
 6    :param percent_grade: A grade as a percent
 7    :return: Letter grade for the provided percentage
 8    """
 9    letter_grade = ""
10    if percent_grade >= 90:
11        letter_grade = "A+"
12    if percent_grade >= 80:
13        letter_grade = "A"
14    if percent_grade >= 70:
15        letter_grade = "B"
16    if percent_grade >= 60:
17        letter_grade = "C"
18    if percent_grade >= 50:
19        letter_grade = "D"
20    else:
21        letter_grade = "F"
22    return letter_grade
  • The above example letter_grade_broken may be one of the first ideas you come up with, but unfortunately it has a problem

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

    • letter_grade_broken(99) would actually return "D"

  • The trick to understanding the problem is to take our time and look at the code

    • 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

    • Assign letter_grade the value "A"

  • 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)

  • But arguably the better 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

  • In other words, as soon as one of the ifs is true, all other ifs are skipped and the program continues running after the else

  • When using elifs, always 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    :rtype: str
 6    :param percent_grade: A grade as a percent
 7    :return: Letter grade for the provided percentage
 8    """
 9    letter_grade = ""
10    if percent_grade >= 90:
11        letter_grade = "A+"
12    elif percent_grade >= 80:
13        letter_grade = "A"
14    elif percent_grade >= 70:
15        letter_grade = "B"
16    elif percent_grade >= 60:
17        letter_grade = "C"
18    elif percent_grade >= 50:
19        letter_grade = "D"
20    else:
21        letter_grade = "F"
22    return letter_grade

8.5. Nesting Conditionals

../../_images/dolls.jpeg
  • 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")

8.6. For Next Class