Find us on GitHub

Teaching basic lab skills
for research computing

Python: Control Flow

Python/Control Flow at YouTube

Hello, and welcome to the third episode of the Software Carpentry lecture on Python. This episode will introduce two forms of control flow: while loops, and if/else conditionals.

Arithmetic and assignment statements are all very well, but the real power of programs come from:

repetition,

which is the ability to do something many times, and

selection,

which is the ability to do one thing rather than another.

The simplest form of repetition is the while loop, which does something as long as some condition is true.

For example, here is a small program that prints out the numbers 3, 2, and 1 using a while loop.

The loop opens with the keyword while, followed by the loop's controlling test. Since this test is true—i.e., since num_moons is currently greater than 3…

…Python executes the statements in the loop body, which is the indented statements under the loop control.

This prints out '3' and subtracts 1 from num_moons.

Python then re-checks the condition. It's still true…

…so the program prints '2' and subtracts 1 from num_moons again.

Another check, another print statement: the program prints '1', then decrements num_moons again. Since the loop's controlling condition is now false, the program is done.

A while loop may execute any number of times, including zero.

Here, for example, num_moons is initially -3…

…so the loop condition is false the first time it is tested…

…and the loop body doesn't execute at all.

The output is therefore the two lines 'before' and 'after', with nothing in between.

It's important to keep this "zero pass" condition in mind when designing and testing code.

A while loop may also execute forever.

Here's another copy of the program that doesn't subtract 1 from num_moons inside the loop body.

It prints 'before'…

…then 3…

…then 3 again…

…then another 3…

and so on.

Since the loop's control condition was true when the loop started, and nothing happens inside the loop to change it, the loop will run until the user gets bored enough to halt it.

This is usually not the desired behavior…

…but there are cases we'll see later where an apparently infinite loop is useful.

Why does Python use indentation to show which statements are in the loop body, when other languages use begin...end or curly braces?

Because studies have shown that's what people actually pay attention to.

Every textbook on C or Java has examples where indentation and bracing don't match, which makes it harder to see the errors in programs. Python simply avoids the problem.

It doesn't matter how much indentation you use, but the whole block must be consistent, i.e., if the first statement is indented by four spaces, the rest of the block must be indented by the same amount.

The Python style guide (known as PEP-8) recommends 4-space indentation.

And please use spaces, rather than tabs, since different editors display tab characters with different widths.

while loops allow programs to repeat things; if, elif, and else allow them to make choices.

Here's an example:

Since moons is not less than zero…

…the program does not execute the first block of code (which would print 'less').

Similarly, moons isn't zero…

…so the second block isn't executed, either.

Finally, since no other choice was taken…

…the statements under the else are executed…

…and the program prints 'greater'.

A set of choices like this always starts with if and a condition…

It can have zero or more elif clauses, each with its own test…

And optionally an else with no test to execute if nothing else is done.

Tests are always tried in order; as soon as one test is true, its block of statements is executed, and no other branch is tested.

Of course, blocks of code may contain other blocks.

For example, in this program an if is nested inside a while.

The while counts from 0 to 10.

And the if prints out odd numbers.

The combination prints out all the odd numbers between 0 and 10.

Here's a better way to do the same thing:

start num at 1, and add 2 to it each time through the loop.

The loop runs half as many times, but the output is the same.

Writing a simple program that works, then tweaking it to make it more efficient, is a common pattern in programming. Another is to write programs top-down, solving one problem at a time.

To see how this works, let's write a program to print out all the prime numbers less than 1000.

The skeleton is simple: start with 2 (since 1 isn't a prime), and loop up to 1000. If the number is prime, print it out.

By definition, a number is prime if it cannot be evenly divided by anything except 1 and itself.

The simplest way to check is to try dividing it by each number that's less than it, so we write another loop.

Saying that it can be divided evenly is the same as saying that there's no remainder…

…which tells us how to fill in the last bit of our program.

Putting the whole thing together gives us this, which is just the bits of code we wrote put in order.

Now let's look at a more efficient version.

This code is almost the same as what we just saw…

…but it takes advantage of the fact that a number can't be evenly divided by anything greater than its own square root. It's a neat idea, and can save a lot of work, but unfortunately this program contains a bug.

As a lot of programmers will tell you, any code that hasn't been tested is almost certainly wrong.

Let's change the bound on the outer loop so that we only print out the primes up to 10.

Here's its output.

Whoops: 4 and 9 aren't prime.

Where's the bug?

One clue is that 4 and 9 are both perfect squares.

So let's have a look at the one thing we changed: the condition of the inner loop.

Two squared is four…

…but since the loop condition is less than, we never actually check to see whether 4 can be divided by 2…

…or whether 9 can be divided by 3, and so on. The condition should be "less than or equal"; if we make this change, the program produces the correct output once again.

This is a common pattern in programming: write a simple version that's easy to check, then gradually introduce complications, checking for bugs and fixing them at each step, to produce something that's faster or more powerful, but still correct.