13. Conditionals, I: if, else and more

13.1. Conditionals and program flow

By default, the Python interpreter will start at the top of a script file and move downwards, systematically executing each successive line exactly once until it reaches the end of the file. This is what would happen if our script only contained the features we have discussed so far: math functions, operator expressions and assignment of variables.

However, we might want to set up our code to calculate some values, and then execute certain lines only in the case of a particular outcome. For example, if a number is a perfect square, print it with text that has an exclamation point. Or we could check a result, and if it were positive do one set of commands (say, double it and add three), but if it were negative do a different set (say, cube it and subtract five). That is, we evaluate a result and choose what to do next. And in this case, it is possible that not every line of code will be executed: some might be skipped over (e.g., if we found no perfect squares or no odd numbers, in the examples above). We are affecting and controlling the flow of the program in a non-trivial---and hopefully useful!---way.

This kind of decision making is called branching, as if one were climbing a tree and deciding which of two branches to follow. Branching command flow can be done using a conditional statement: we establish a condition, and if it evaluates to true, Python executes commands A, B, C, ...; otherwise, it executes X, Y, Z, ... This is another fundamental item in our toolbox of algorithmic programming, and there is a lot of flexibility which how conditions are structured.

The most common way to start a conditional in an English sentence or math text is using the word if. This is also the keyword to start one in Python. Therefore, conditionals are also known as if statements. There is a family of them, with related meaning and syntax, but differing in the number of branching points. We explore each briefly in turn.

Note

While a spoken language may have many synonyms like "in the case of" or "in the event that", within the programs there will only be if: what it lacks in imagination, it makes up for with clarity.

Here and in general, it is useful to be able to map back and forth between a conceptual/colloquial expressions and a strict program syntax. Part of the art of programming is often to rephrase a question or element of a problem in a helpful way.

13.2. If statements

In words, a basic conditional statement can be expressed as, "If some condition is met, then do these specific commands." This conditional phrasing can be mapped into Python syntax as:

if <some condition> :
    <specific commands>

And, without adding any further text, this also implies that when that condition is not met, then those specific commands will not be done. (Other members of the if-condition family deal with these aspects differently, discussed below).

There are several important things to note about the conditional's syntax (particularly since we will see some similar features of this syntax in several other Python structures):

  • if is a keyword in Python, and it is recognized as the beginning of the conditional.

  • The some condition is a statement (also known as a "predicate") that immediately follows the keyword if. It is an expression that Python will interpret as a Boolean value: whatever form it takes it is evaluated as either True or False. Note that the statement may be complicated and contain several pieces (numbers, relational operators, comparisons, etc.), but it is evaluated as a single True or False.

  • A colon : follows the statement, and it is necessary syntax to define the end of the statement. If you want to think about mapping this code to a spoken sentence, then in this context you could think of this as the "then".

  • The line following the if statement must be indented relative to if; indentation is the set of spaces at the start of the line. The Python standard for indentation is 4 spaces.

  • The <specific commands> to be done may occupy one or more lines. Each line must be indented the same amount. That is how Python recognizes what is "inside" this if condition branch (i.e., all to be done if the statement evaluates to True).

  • The Python interpreter knows that it has reached the end of the list of the <specific commands> to do when it either:

    1. Reaches a line that is not indented relative to if,

    2. Reaches the end of file (EOF), or the end of the Jupyter-notebook cell.

So, for example, assume we have some int or float x that is defined. Then the conditional, "If x is negative, calculate x-cubed and print the result," could be translated as:

1if x < 0 :
2    xcub = x**3
3    print("x-cubed is:", xcub)
4print("I am printing from *outside* the condition!")

Line 1 starts the conditional and provides the statement to be evaluated as True or False. After Line 1's colon, the Python interpreter starts looking immediately for indented lines, and it finds two of them: Lines 2-3 will be executed only if x is less than zero. Line 4 is a bit extra: it is not indented, so it is one way to signal the end of the conditional branch. Python recognized it as being outside the branch, and will be executed regardless of the value of x. We will see 2 lines printed if x is less than zero, otherwise just one line.

Here are some other examples of valid if conditions (again, assuming each primary variable has been properly a assigned value already):

  • Chained comparison, Example 1:

    if 100 <= x < 1000 :
        print("Medium-sized x", x)
    
  • Arbitrarily large expressions can be tested, Example 2:

    if type(my_value) == int and my_value > 250 :
        print("Oh good, this value is both an int")
        print("... and it is pretty large:", my_value)
    
  • Check a property of a variable, and possibly re-define it, Example 3:

    if not(type(y) == int) :
        print("Oh darn, a non-integer value:", y)
        y = int(y)
        print("I have now converted that variable value to be an int:", y)
    print("Whether you wanted it or not, you have an int now!")
    

Q: How many print() function calls are made for each of Ex. 1, 2 and 3 above if the conditional statement evaluates to True? How many, if it evaluates to False?

+ show/hide response

The following are invalid if-conditions, because they have incorrect syntax in various ways:

  • Bad "if" syntax, Example 4:

    1if 100 <= x and x < 1000
    2    print("Medium-sized x", x)
    

    ... which produces:

    1   File "<ipython-input-33-d7fec1c0e4e4>", line 1
    2     if 100 <= x and x < 1000
    3                              ^
    4 SyntaxError: invalid syntax
    

    Why is this a SyntaxError? There is a missing colon after the predicate statement. Note that the interpreter has picked out a problem in "line 1", and see where it is pointing an arrow ^.

  • Bad "if" syntax, Example 5:

    1if 100 <= x and x < 1000 :
    2print("Medium-sized x", x)
    

    ... which produces:

    1   File "<ipython-input-34-303dd3ff9dbf>", line 2
    2     print("Medium-sized x", x)
    3         ^
    4 IndentationError: expected an indented block
    

    Why is this an IndentationError? There is missing indentation after the colon. Note that the interpreter picks out a problem in "line 2", and its arrow ^ is pointing at the command print, which it expects to be indented (since the line immediately follows the :).

Mini-summary: This if conditional allows us to program the following kinds of expressions, "If X is true, do Y," where X can be as complicated a statement as desired, and Y can be a list of one or more commands. Python demands certain syntax to be able to interpret this, including the colon : at the end of the predicate statement X and indentation of each of the conditional commands in Y.

13.2.1. Notes about indentation

Conceptual comment

As noted above, Python syntax uses indentation (whitespace at the beginning of a line) to denote what is inside the if condition and where it ends. This is actually a pretty nice syntax to have, because it provides a strong visual cue for both the start and duration of the conditional. Even in programming languages that do no require indentation for conditions, many people tend to still indent anyways, to clearly identify to themselves and other programmers where the commands inside the condition are.

We can think about this conceptually like the form an outline is written: a section is introduced by a heading, and then items that belong in that section are indented. It is visually apparent when a new heading comes along, because it is unindented back to the level of the other headings (or the list ends). For example, we could compile a list of operators for different Boolean expressions:

Logical operators:
intersection, and
union, or
negation, not
Membership operators:
containment, in
absence, not in
Comparison operators:
equality, ==
inequality, !=
greater than, less than, >, <
greater than or equals, less than or equals, >=, <=

As one reads down the list, it is quickly apparent where the heading items are and which items are "inside" each heading's section. Parallel headings have the parallel indentation---that is, indentation of the same size, which is zero here. We can have one, two, a hundred or more under each heading. Keeping each item indented an equal amount is useful for visual recognition (and in Python, it is required).

We put this conceptual note here because we will use indentation a lot in Python programming. Below, we will use it with more members of the conditional family. Later, we will use the syntax in writing loops and functions. Much later, we will use it in nesting structures (containing conditions inside conditions, or inside loops, etc.), and indentation will still be used to organize these levels.

Implementation suggestion

When writing code, indentation may be made up of spaces and/or TABs. Tabs are convenient to indent with a single keystroke, but they are also unstable, because different editors and/or computers will represent them with different lengths. This can break Python code! Therefore, for portability to different computers and text editors, using only spaces is highly preferred.

In most text editors, you can set TABs to be replaced by spaces. So, you can still conveniently use the TAB key while also instantly removing the instability. The typical indent size of 4 spaces balances readability with space considerations, but you can choose a different indentation size as long as you are consistent with it in a code.

13.3. If/else statements

The above "if" conditional only provides commands for when a statement is True; it only offers one branch. The related if/else conditional adds the ability to provide alternative commands if the predicate statement is False, adding a second branch.

The "if/else" conditional statement can be generally expressed as, "If some condition is met, then do these specific commands; otherwise, do these other specific commands." This translates into Python as:

if <some condition> :
    <specific commands>
else:
    <other specific commands>

Note that all of the above rules for the if branch apply, such as the colon and indentation syntax. Now, we have a new branch that starts with the keyword else (the Python translation of "otherwise"). There is no statement following else, simply a colon :. Note that else is not indented relative to if: this makes it visually apparent that they are parallel branches (e.g., like in an "outline", described above), and it also closes the if branch's <set of things> list. The same rules apply for the else branch's <other specific commands> as for the if branch's <specific commands>: there may be one or more, and each must be indented from else by a constant amount. Python again knows the end of the else list of commands when either the next line is not indented or the file ends.

Here are some examples:

  1. An example of categorizing a random integer:

    1x = random.randint(0, 99)   # get a random int in interval [0, 99]
    2if x < 50 :
    3    print("This number is small:", x)
    4else:
    5    print("This number is big:", x)
    6print("Done checking the number")
    
  2. We convert one of the "if" examples above to an "if/else" case:

    1y = <some value>
    2if type(y) == int :
    3    print("Oh good, an integer value:", y)
    4else:
    5    print("Oh darn, a non-integer value:", y)
    6    y = int(y)
    7    print("Now I have *made* it an integer value:", y)
    8print("Whether you wanted it or not, you have an int now!")
    

    The cases of whether y is or is not an int are made explicit here. The final print function is outside of the conditional (which is signaled by its lack of indentation relative to the if/else keywords). From a flow point of view, Lines 1 and 2 are always executed. If y is an int, then Line 3 will executed, and then Python skips the entire other branch, jumping to Line 8; otherwise, Python jumps from Line 2 to Line 4, and will execute lines 5-7 within this branch, and then exit the branch and continue to Line 8.

    Again, Python's use of indentation provides a nice, human-readable visual cue to check what is inside of a conditional branch. We can immediately tell that there is one command following the if branch, and three commands following the else branch. We also know that one more command will be executed after the condition.

  3. Let's translate the following into a block of Python code: "Let x be a number. If it has 9 as a factor, print how many times 9 goes into it; otherwise, just print the original number."

    First, we have more clearly state our predicate statement, translating it into a mathematical Boolean statement. What is a mathematical expression we can use to check if one number is a factor of another? (Hint: could we test something about the remainder of division? We do have an operator to calculate that ...)

    OK, so we could rewrite the problem in a more mathematical way: "Let x be a number. If the remainder of x/9 is zero, then ..., else ..." This is now something that can be translated into Python code:

    1x = random.randint(0, 99)    # an arbitrary way to get a value to check
    2if x % 9 == 0 :
    3   print("How many times does 9 go into x?", x//9)
    4else:
    5   print("Sadly, this number does not have 9 as a factor:", x)
    

    Framing a question or scenario properly is one of the most important aspects of programming. It occurs before one starts typing code. Breaking a problem down into smaller pieces and formulating each one clearly is vital, and it takes practice. That is precisely why why we are practicing here!

The following are some invalid attempts to use if/else, based on examples presented above:

  • Bad "if/else" syntax, Example 1:

    1y = 25.0
    2if type(y) == int :
    3    print("Oh good, an integer value:", y)
    4    else:
    5        print("Oh darn, a non-integer value:", y)
    6        y = int(y)
    7        print("Now I have *made* it an integer value:", y)
    8print("Whether you wanted it or not, you have an int now!")
    

    ... which produces:

    1  File "<ipython-input-71-a95a1bfa6aef>", line 4
    2    else:
    3       ^
    4SyntaxError: invalid syntax
    

    Why is this a SyntaxError? For else to be valid, it must be parallel to an if. As we said above, parallel branches must have the same indentation level. That is not the case here, and so Python is rightfully unhappy. To fix this, one could unindent else, as well as the three lines following it (they should just have a single level of indentation from their starting keyword).

  • Bad "if/else" syntax, Example 2:

    1y = 25.0
    2if type(y) == int :
    3    print("Oh good, an integer value:", y)
    4else:
    5    print("Oh darn, a non-integer value:", y)
    6y = int(y)
    7    print("Now I have *made* it an integer value:", y)
    8print("Whether you wanted it or not, you have an int now!")
    

    ... which produces:

    1  File "<ipython-input-73-077825dcb735>", line 7
    2    print("Now I have *made* it an integer value:", y)
    3    ^
    4IndentationError: unexpected indent
    

    Why is this an IndentationError? And why is Python complaining about Line 7? Looking at the original example, we can see that the badness (or at least alteration) comes from Line 6 not being indented.

    Well, let's think about the interpeter's flow: It interprets Line 1 fine; then Line 2 fine; and since the predicate statement evaluates to False, the interpreter jumps to Line 4 and enters the else branch, starting with Line 5, which is indented. Then, since Line 6 is not indented relative to the else, the interpreter believes that the else branch is complete, and that it is outside the conditional. Therefore, when the interpreter sees Line 7 indented, it is surprised and unhappy, because there is no reason for it to be indented. Hence, our mis-typing in Line 6 caused an error for the interpreter in Line 7. This is an example of a subtle kind of error that can occur in programming, and which can be difficult to debug if we are not careful.

    This provides a good lesson, too, about Python's debugging. As we have seen above, it is quite useful and can often point to the actual location of the coding error. However, it cannot read our minds, and sometimes an earlier (or "upstream) mistake in the code is actually the root cause of an error. Also, if Line 7 weren't included or had also been mistakenly unindented, the interpreter might not even have found any error here---yikes! This is why we must always test our code as we go along, to make sure subtle errors don't creep in.

  • Bad "if/else" typing (but allowed syntax), Example 3:

    1y = 25.0
    2if type(y) == int :
    3    print("Oh good, an integer value:", y)
    4else:
    5    print("Oh darn, a non-integer value:", y)
    6    y = int(y)
    7print("Now I have *made* it an integer value:", y)
    8print("Whether you wanted it or not, you have an int now!")
    

    ... which produces: ... no error!

    However, relative to our code above, we know that this is a mistake. As typed, the string "Now I have made ..." would always be printed, which is not correct. In terms of our meaning, this line should be indented, but there is no Python syntax error for the interpreter to find and report, so we see no warning. Again, we programmers must be careful because things like this can be tricky to debug.

In summary, the if/else conditional allows us to program the following kinds of expressions, "If X is true, do Y; otherwise (=else) do Z." Each of the Y and Z will be one or more commands, and the same syntax rules apply for each---namely, each set is constantly indented within their own section. The else line ends in a colon, just as the if line does. The "else" is logically parallel to the "if", and in programming syntax this is mirrored by the fact that else has no indentation relative to its partnered if.

13.4. If/elif/else statements

Finally, the branching conditional can be generalized to test more than one (separate) predicate statements, using one or more else if lines. This allows there to be any number of branches.

The if/elif/else conditional can be expressed (trying give a sense of the wide generality) as, "If some condition is met, then do these specific commands; otherwise, if this other condition is met, do these other specific commands; or otherwise, if this still other condition is met, do these other specific commands; ... and finally, if none of those conditions is met, do these last specific commands." This conditional is translated into Python as:

if <some condition> :
    <specific commands>
elif <some other condition> :
    <other specific commands>
elif <yet another condition> :
    <still other specific commands>
else:
    <fallback specific commands>

There can be any number of elifs, and all the rules that we described for the if line apply to the elif line. Note that there doesn't even need to be a final else, if one does not want a "fallback" case of doing something when no tested predicate is true. Basically, this structure generalizes all of the conditionals discussed above.

Note: at most only one branch in a conditional can be executed. Even though multiple predicates could evaluate to True, Python will go through in order, find the first one that is True and execute its branch of commands, and then jump to the end of the conditional immediately.

Sometimes, the order of statements in the if/elif sequence doesn't really matter. This is only the case if the predicate statements are non-overlapping (or "mutually orthogonal"). For example, whether we first test if the remainder is 1 or 0 would not affect this outcome:

1x = random.randint(0, 99)    # an arbitrary way to get a value to check
2if x % 3 == 0 :
3   print(x, "is perfectly divisible by 3.")
4elif x % 3 == 1 :
5   print("We get a remainder of *1* when dividing", x, "by 3.")
6else:
7   print("By process of elimination we know the following:")
8   print("... that we get a remainder of *2* when dividing", x, "by 3.")

However, there are many applications where that is not the case, and the order of the conditions is critical, because only the branch of the first true one will be executed. Consider the logic of the following construction:

 1dist_km = int(1000*random.random())
 2if dist_km > 500 :
 3    print("Far distance.  Looks like I'm flyin'!")
 4elif dist_km > 200 :
 5    print("Well, I will check a train schedule!")
 6elif dist_km > 49 :
 7    print("Not too bad, I guess that's a bus ride.")
 8elif dist_km > 5 :
 9    print("Maybe my friend can drive.")
10elif dist_km > 2 :
11    print("Time for a jog! I wanted some exercise, anyways.")
12else:
13    print("OK, I will walk.")
14print("... just to travel", dist_km, "km.")

What is the flow when dist_km is 300, and each of those elif statements is actually True? Well, the statement in Line 2 evaluates to False, so Python skips its branch and jumps to the next parallel conditional keyword, elif in Line 4. The statement there is True, so Python executes its branch (Line 5). It then skips all remaining branches, jumping to Line 14. But if we swapped the branches that start in Lines 4 and 10, would lead to different outcomes (and ones that make less sense, based on the logic of traveling).

In summary:

  • Conditionals allow us to control the code flow, so that subsets of commands are executed only under certain conditions.

  • Each branch of a conditional can contain one or more commands.

  • With the addition of else and elif, if conditions are quite flexible for specifying a range of conditions and alternatives.

  • When we have elif statements, we should make sure that they are ordered in way that makes sense, particularly when tested criteria overlap.

  • At most, only one branch of any conditional will be executed.

  • Indentation is key to defining the structure of a conditional. Indentation determines:

    • what commands are in a given branch;

    • where the end of the conditional is

    • which if/elif/else branches are in parallel (esp. when we see later how conditionals can be nested inside each other).


13.5. Practice

  1. For each of the following, ponder first, then check in the terminal.

    1. How many print statements get evaluated here?

      x = 3
      if x > 25:
          print("This number is big!")
          print("Where did you find it?")
      print(x)
      
    2. How many print statements get evaluated here?

      x = 3
      if x > 25:
          print("This number is big!")
      elif x < 10:
          print("This number is small!")
      print(x)
      
  2. Fix the following:

    x = 3
    if x > 25
        print("This number is big!")
    
  3. Fix the following:

    x = 3
    if x > 25
        print("This number is big!")
        else:
            print("This number is small!")
    
  4. Can you have an if without an else? Can you have an else without an if?

  5. Write an if condition to check if a variable called test1 is greater than 5. If it is, print "Congratulations".

  6. Write an if condition to check if a variable called test2 is odd. If it is, print "I found an odd number"; otherwise, print "I found an even number".

  7. Write an if condition to check if a variable called test3 is not an int. If it is not, then print "Fancy number". Otherwise, do nothing.

  8. Write an if condition to check if a variable called test4 is in the interval [-3, 3]. If it is, print "Small number!". If it outside that range but in the range [-10, 10], print "Medium number!"; and if it is outside of that range, print "Big number!"

  9. Write an if condition to check if a variable called test5 is complex or a float. If it is complex, print "I found a complex number"; if it is a float, print "I found a float"; otherwise, print "Oh well".

  10. Write an if condition to check if a variable called test6 is a negative integer. If it is, multiply it by -3 and see if the result is a multiple of 9; if that is, then print "hello, my friend". In any other case, print "Oh, no, not you".

  11. Make a conditional that checks the type of a variable named test7 for each of the following that are familiar to us: int, float, str, bool and complex; if the type is none of the above, then just print "this type is unfamiliar!", along with the type. In each case case of a familiar type, print the type, and if it is: int, print the square of it; float, the cube of it; bool, the opposite of it; complex, the magnitude of it; and string, 4 copies of it appended together.

  12. Write a program that prompts for two numbers, a and b. If the b is not zero, print the quotient a/b; otherwise, print an error message that division by zero is not allowed.

  13. Write a program that prompts for a string s. If the length of the string s is odd, then print it with 'ing' appended to its end. If the string length is even and non-null, print the first (i.e. [0]th) character and the string's length. If the string is null, print "Empty!". (NB: think about the simplest ordering for presenting these conditions for most efficient/simplest code).

  14. Write a program that prompts for 3 numbers to be saved as a, b and c, and prints them in ascending order.

  15. Write a program that prompts for a score between 0 and 100 (inclusively). If the value of score is out of range, print an error message. If score is in the appropriate range, check whether it is high (score >= 80) or medium (50 <= score < 80) or failing (score < 50).

  16. Write a program that prompts the user for 2 integers a and b, and then solves the quadratic equation x^2 + ax + b =
0.