Booleans in Python (Part 3)

Example: determining multiples

As another example, let's write a function that determines if a number is a multiple of a factor. We can use the remainder function:

def is_multiple(number, factor):
    return number % factor == 0

Problem:

However, if the factor is zero, we will see an error message telling us not to divide by zero.

Solution:

We want to return the answer False if factor is zero, so we can add another condition checking that factor is non-zero:

def safe_is_multiple(number, factor):
    return factor != 0 and number % factor == 0

By putting that condition first, we can rely on short-cut evaluation.


Tracing the function

Let's trace the function safe_is_multiple(10, 5):

safe_is_multiple(10, 5)
    ⇒ 5 != 0 and 10 % 5 == 0

We first substitute 10 in for number and 5 in for factor in the expression returned by the function.


    ⇒ True and 10 % 5 == 0

We now start to evaluate the expression from left to right. The first smaller expression, 5 != 0 is True, so we can substitute the value True for the first expression. Can we use short-cut evaluation to stop early? No, since we are using and, we can only stop early if have a smaller expression that evaluates to False.


    ⇒ True and 0 == 0

So now we evaluate the expression 10 % 5 to obtain 0, which we place instead of 10 % 5 in the expression.


    ⇒ True and True

This results in the expression 0 == 0, which evaluates to True.


    ⇒ True

Finally, the expression True and True is evaluated to obtain True.


Here's the complete trace when all parts are put together:

safe_is_multiple(10, 5)
    ⇒ 5 != 0 and 10 % 5 == 0
    ⇒ True and 10 % 5 == 0
    ⇒ True and 0 == 0
    ⇒ True and True
    ⇒ True

Now let's trace safe_is_multiple(10, 0):

safe_is_multiple(10, 0)
    ⇒ 0 != 0  and 10 % 0 == 0

We substitue 10 in for number and 0 in for factor.


    ⇒ False  and 10 % 0 == 0

On inputs 10 and 0, the first expression evaluates to False.


    ⇒ False

Since we are using short-cut evalution, we can now conclude that the value of the entire expression is False. This not only saved time, but saved us from the program failing due to an error message. That is, since we never evaluated the second smaller expression, 10 % 0 == 0, we didn't try to divide by zero. Notice that the only time we would divide by zero would be if the second input were zero. But that is exactly when we would get to stop early. Short-cut evaluation ensures that we never see an error message, no matter what integers the inputs are.


Here's the complete trace when all parts are put together:

safe_is_multiple(10, 0)
    ⇒ 0 != 0  and 10 % 0 == 0
    ⇒ False  and 10 % 0 == 0
    ⇒ False

Example: checking the first character

We can also take advantage of short-cut evaluation in writing a function to determine whether the first character in a string is the letter "a". Consider the following function:

def starts_with_a(word):
    return word[0] == "a"

Problem: This function seems fine except that trying to find the first character of an empty string will result in an error message.

Solution: We want to return False in that case anyway, so we can add a condition that will be False for the empty string. Again, we put the condition first to take advantage of short-cut evaluation:

def starts_with_a(word):
    return len(word) > 0 and word[0] == "a"

Tracing the function

As in the previous example, we can trace the behaviour of our function. We will first trace the function when the input string is not empty.

starts_with_a("dog")
    ⇒len("dog") > 0 and "dog"[0] == "a"

We first place the input values everywhere in the expression returned by the function.


    ⇒ 3 > 0 and "dog"[0] == "a"

Moving from left to right, we first evaluate len("dog") to obtain 3.


    ⇒ True and "dog"[0] == "a"

Then, 3 > 0 evaluates to True. We don't get to stop early, since we only get to stop early when we see False.


    ⇒ True  and "d" == "a"

We next determine the first character in "dog", "dog"[0].


    ⇒ True  and False

Then, "d" == "a" evaluates to False.


    ⇒ False

This means that the entire expression is False, since the value of True and False is False.


Here's the complete trace when all parts are put together:

starts_with_a("dog")
    ⇒ len("dog") > 0 and "dog"[0] == "a"
    ⇒ 3 > 0 and "dog"[0] == "a"
    ⇒ True and "dog"[0] == "a"
    ⇒ True  and "d" == "a"
    ⇒ True  and False
    ⇒ False

Now let's trace the function when the input string is the empty string.

starts_with_a("")
    ⇒ len("") > 0 and ""[0] == "a"

We input the empty string "".


    ⇒ 0 > 0 and ""[0] == "a"

Then, len("") evaluates to 0.


    ⇒ False and ""[0] == "a"

Hence, the value of the first expression, 0 > 0, is False.


    ⇒ False

By short-cut evaluation, we get to stop early and returns False without ever trying to find the first character in the string. This is very good, because it meant that we avoided applying the index 0 to an empty string, ""[0], which would have resulted in an error message. In other words, short-cut evaluation allowed us to avoid extracting an element from an empty string. Again, short-cut evaluation saved the day: it is only when there is an empty string that the first expression will evaluate to False, and that is precisely when we want to stop evaluating expressions before hitting the second one.


Here's the complete trace when all parts are put together:

starts_with_a("")
    ⇒ len("") > 0 and ""[0] == "a"
    ⇒ 0 > 0 and ""[0] == "a"
    ⇒ False and ""[0] == "a"
    ⇒ False

Taking advantage of short-cut evaluation

Situations that cause errors:

  • Dividing by a number that is equal to zero.
  • Extracting the first character of a string that is empty.
  • Adding 6 to a value that is not a number.

Keep short-cut evaluation in mind to avoid errors such as division by zero, extracting a character from an empty string, and adding non-numbers. Judicious ordering of conditions can allow you to take advantage of short-cut evaluation to circumvent these problems.