Back to Article
PE100-03: Decision Structures
Download Notebook

PE100-03: Decision Structures

In the first lesson, everything we did was sequential programming. Statements are executed one after the other in exactly the order they’re written in. As long as there aren’t any errors, every statement will be executed.

The Simplest “if” Statement

In almost any real Jupyter notebook or standalone program we write, there will have to be places where different code paths are taken depending on what has happened leading up to there. Suppose we’re looking at absorption at one specific wavelength and we know that some of our instruments are a little bit too sensitive to changes in humidity. Maybe the first spectrometer has some insulation that is just a little too porous and reads a bit high, but the second one is even worse. We have calibration constants we can apply, but we have to apply the right constant for each individual instrument.

In [1]:
spectrometer_number = 1                                                        
reading = 7.00041                                                              
                                                                               
if spectrometer_number == 1:                                                   
    useful_result = reading * 1.077                                            
                                                                               
useful_result
7.539441569999999

Here we have the first Decision Structure (also called control flow statement) that we’ll look at. Taking the above code apart, we see several important things.

  1. This is an “if statement”.
  2. Testing to see if two things are equal is done with two equals signs, not one (==). There’s a historical reason for this, and it’s a good reason, but it always trips up newcomers. You have been warned. You’re welcome.
  3. The last character on the if line is : (a colon ).
  4. The “body” of the if statement, the part that is run if and only if the tested condition is met, is indented.

In the case of the above if statement, what the code does is check to see if we’re using spectrometer number 1 and if we are then we add 7.7% to the reading and save it in a variable called “useful_result”.

Else: the catch-all specialist

If that was all an if statement could do then it would be really useful. But that’s not all it can do. We need to do something reasonable when we get readings from the second instrument. Such as:

In [3]:
spectrometer_number = 2                                                         
reading = 7.00041                                                               
                                                                                
if spectrometer_number == 1:                                                    
    useful_result = reading * 1.077                                             
else:                                                                           
    useful_result = reading * 1.19

useful_result
8.3304879

Here we have added an “else clause”. The above code is interpreted as “check to see if we’re using spectrometer number 1 and if we are then we add 7.7% to the reading and save it in a variable called useful_result. Otherwise, set useful_result to whatever is saved in”reading” plus 19%.

So far, so good. But there’s more! Suppose we need to handle several of these not-quite-top-quality spectrometers. How do you suppose we could deal with that? We could resort to putting if-else statements inside if-else statements in sort of a brute force fashion…

In [4]:
spectrometer_number = 3                                                         
reading = 7.00041                                                               
                                                                                
if spectrometer_number == 1:                                                    
    useful_result = reading * 1.077                                             
else:                                                                           
    if spectrometer_number == 2:                                                
        useful_result = reading * 1.19                                          
    else:                                                                       
        if spectrometer_number == 3:                                            
            useful_result = reading * .92                                       
                                                                                
useful_result
6.4403771999999995

The above code looks a little intimidating, but all there is to it is just a series of if statements. The logic of it goes like this: “If the instrument number is 1, then adjust it 7.7% and we’re done. Otherwise, it must be some other instrument number, so run our else clause”. Then in the else clause, it does the same thing, except checking for the second instrument and adjusting by 19%. If there was nothing to do there (because the instrument number was 3) then we run the else clause of that second if statement. This else clause houses an if statement that checks to see if the instrument is number three. This time it is, so the body of the if statement is executed. We set useful_reading equal to 92% of reading.

Elif

This is fine if we only have three instruments, but what do we do if we have 20 of them? We could, in principle, type in 60 lines of code, but that would be tedious, error prone, and would take a while to read and find any mistakes. Of course there’s a better way.

That better way is the “elif” keyword.

Let’s see an example with 5 instruments…

In [5]:
spectrometer_number = 4                                                         
reading = 7.00041                                                               
                                                                                
if spectrometer_number == 1:                                                    
    useful_result = reading * 1.077                                             
elif spectrometer_number == 2:                                                  
    useful_result = reading * 1.19                                              
elif spectrometer_number == 3:                                                  
    useful_result = reading * .92                                               
elif spectrometer_number == 4:                                                  
    useful_result = reading * 1.03                                              
elif spectrometer_number == 5:                                                  
    useful_result = reading * 1.26                                              
else:                                                                           
    useful_result = reading                                                     
    print("Be careful!")    
                                                                                
useful_result
7.210422299999999

The final else clause is the one that runs if no other clauses ran. If no clause’s conditional statement is true so no clause runs, whether it’s the if clause or any of the elif clauses, then the else clause runs. It’s really easy to spot else clauses even from across the room - they’re the ones that don’t have a conditional test.

Note that the if, elif, and else lines must end with a colon. True confession time: I forget the colons about half the time. Python catches it as an error, I fix it, and life goes on.

Slightly More Complicated

You can run more than one line of code in response to the tested conditions, but they have to be indented the same amount:

In [6]:
spectrometer_number = 103
reading = 7.00041

if spectrometer_number == 1:
    useful_result = reading * 1.077
    trustworthy = False
elif spectrometer_number == 2:
    useful_result = reading * 1.19
    trustworthy = False
else:
    useful_result = reading
    trustworthy = True

print(useful_result, trustworthy)
7.00041 True

There are four interesting things going on here. The first and most important thing to notice is that we’ve got more than one line of code running in response to an “if”, “elif”, or “else” clause. A collection of lines that should be run together as a whole is called a code block. Unlike many languages that mark the start and end of code blocks with special words or characters, Python just does it by using indentation. Everything that is indented the same amount is considered to be in the same code block. We’ll look at this in more detail in a few minutes.

Secondly, we’ve added lines to set a variable named “trustworthy” to a value depending on whether we had to adjust the reading. Evidently, if we have to compensate for old, dry, cracking insulators then we don’t really trust the instrument.

The third interesting thing is the values True and False. These are “Boolean” values, and when we put them into the “trustworthy” variable then it takes on the Boolean type. There are only two values, True and False. The capitalization is important.

The fourth thing to notice is that we’re sending two values into the print statement and it’s printing both of them. In general, we can give the print statement any number of arguments, separated by commas, and it will print all of them separated by one space.

Conditional (aka Relational) Operators

The conditional test in each part of an if statement is an expression that results in a Boolean value. So far, the only conditional operator (or relational operator) we’ve seen is ==. There are others, though. For the sake of completeness, I’ll include == here:

operator tested condition
== equals
!= not equals
> greater than
>= greater than or equal
< less than
<= less than or equal

“Relational” has at least two meanings in computing. Relational Operators have nothing to do with Releational Databases.

Try This

For each of the following code cells, decide what the result is, run the cell, and see how you did:

In [8]:
5 < 6
True
In [7]:
5.99 == 5.99
True
In [9]:
5 != 5.00
False
In [10]:
5+6 < 11
False
In [11]:
6 * 6 > 12 + 12 + 12
False

Relational operators also work with strings.

In [14]:
name = "Alice"
if name == "Alice":
    print("equals Alice.")
if name != "Bob":
    print("The person is not Bob.")
if "Alice" < "Bob":
    print("Alice comes before Bob in alphabetical order.")
if "Alice" <= "Alice":
    print("Alice comes before or in the same place as Alice in sorted order")
if "Mary" > "Mark":
    print('Working left to right, the M, the a, and the r match on')
    print('both strings, but when we finally get to the y and the k, y comes')
    print('after k in alphabetical order.')
equals Alice.
The person is not Bob.
Alice comes before Bob in alphabetical order.
Alice comes before or in the same place as Alice in sorted order
Working left to right, the M, the a, and the r match on
both strings, but when we finally get to the y and the k, y comes
after k in alphabetical order.

A couple words of caution: the comparisons are based on the ASCII codes for each character. The “A” in ASCII stands for “American”, and as you might expect that means it only works for English language text. If you need to handle other languages, even potentially, then there is a better way to do it and we’ll see that in the lesson on strings.

Also, Capital letters are always less than lowercase letters, and not in the way you might think. “A” is less than “Z”, as you might expect, but “Z” is greater than “a”. The numbers 0-9 are the lowest of all. Punctuation is sprinkled around and the only way to know for sure is to look up “ASCII Chart”.

Code Blocks

Let’s go back to that part about running several lines of code but they have to be indented the same amount. Python always runs “blocks” of code. That block might be as short as one line:

In [17]:
circumference = 40 * 3.14159

circumference
125.6636

or it might be arbitrarily long:

In [19]:
height = 6.01
length = 5.5
width = 14.3
density = 4.2
volume = height * width * length
mass = volume * density
energy_per_gram = 761.3
eyebrow_altering_potential = mass * energy_per_gram

eyebrow_altering_potential
1511396.1762899999

Whether it was the one line example or the eight line one, Python will set out to run all of those lines in one shot, and as long as there aren’t any errors it’ll do it. These are known as code blocks.

The decision structures (again, also called control flow statements) in Python all do basically the same thing: they evaluate an expression and depending on whether it turns out True or False, they execute a code block in some manner. This means that wherever we can have a single line of code running in a decision structure we can have as many lines as we want.

Take a look at the following example. For the four possible combinations of potentially_hazardous and explody, decide what would be printed out. Then try out the combinations and make sure you know why each combination was handled the way it was.

In [2]:
potentially_hazardous = True
explody = True

if potentially_hazardous and explody:
    height = 6.01
    length = 5.5
    width = 14.3
    density = 4.2
    volume = height * width * length
    mass = volume * density
    energy_per_gram = 761.3
    eyebrow_altering_potential_energy = mass * energy_per_gram
    print("Total Available Kaboom (TAK) to ruin your day is", eyebrow_altering_potential_energy)
elif potentially_hazardous:
    print("Not likely to go 'kaboom', but not something you want to casually eat, either.")
    print("I mean, unless you're feeling brave.")
    print("Even then, it's a bad idea.")
elif explody:
    print("This is one of those things that will blow up but isn't actually hazardous.")
    print("I'm guessing it's a vinegar-and-baking-soda volcano.")
else:
    print("As far as we know, the material in quesion is no more")
    print("dangerous than takeout pizza.")
Total Available Kaboom (TAK) to ruin your day is 1511396.1762899999

Did you notice potentially_hazardous and explody? and is a boolean operator. We’ve seen the arithmetic operators already (+, -, *, /, etc.) and now here are the boolean operators. They’re named after Boolean algebra, the algebra of logic, and are used to make larger logical expressions from smaller ones. There are three boolean operators: and, or, and not.

The and operator evaluates to True if both of its arguments are True. The or operator evaluates to True if either or both of its arguments are true. The not operator takes only one argument and reverses it: not turns True into False and False into True.

In [33]:
medical_license = True
dental_license = True

if medical_license and dental_license:
    print("Doctor of Medical Dentistry (DMD)")
elif dental_license and not medical_license:
    print("Plain old dentist.")
elif not dental_license and medical_license:
    print("Garden-variety doctor.")
else:
    print("No license at all. Run. Quickly.")
Doctor of Medical Dentistry (DMD)

Coming Up Next: Loops

At this point, we’ve seen the most basic way to alter the flow of control in Python: the if statement. We can write Python code to solve non-trivial problems now, but there are still some things we need in order to use Python as a truly general-purpose language. In the next notebook we’re going to make our code do something over and over.