Chapter 15: Conditional and Control Statements

The bell in Anaya’s school does not ring by itself. Every morning her teacher, Mr. Rao, watches the clock, and at 7:40 he presses the button. On Sundays he does not press it at all. On examination days he presses it twice — once as a warning, once for the start.

This year the school fitted a small computer to do that job. The computer has to follow the same choices Mr. Rao followed. If it is Sunday, ring nothing. If it is an examination day, ring twice. Otherwise, ring once. And each time it rings, the striker must move for exactly six beats — not five, not seven.

Look at that carefully. Two different kinds of instruction are hiding inside it. One kind picks a path: do this, or else do that. The other kind repeats a job — a fixed number of times, or until something changes. The path the small computer follows is shown in Fig. 15.0-1.

Almost every useful program you will write is built out of these two ideas.

Diagram

Fig. 15.0-1 – simple flowchart of the school bell controller; start at top, a diamond labelled “Is it Sunday?” with a “yes” arrow going straight to Stop and a “no” arrow going down to a second diamond labelled “Is it an exam day?”; “yes” leads to a box “ring twice”, “no” leads to a box “ring once”; from each ring box a small loop with a curved arrow back on itself labelled “6 beats”, then an arrow to Stop

NoteCuriosity Corner

Q1. How does a program pick one path instead of another? Q2. Why does Python care so much about the blank space at the start of a line? Q3. When there are many conditions to check, what is the neatest way to write them? Q4. How can a program repeat a job when nobody knows in advance how many times it must run? Q5. Can a loop be stopped halfway, or made to skip just one round?

This chapter will answer all five of these questions.

15.1 Why a Program Needs to Choose

Every program you have written so far has run in a straight line. Python starts at the first line, does it, moves to the second, does that, and carries on until the last line. Nothing is skipped and nothing is repeated.

name = input("Enter your name: ")
print("Welcome,", name)
print("Have a good day")

This is fine for a task that is the same every single time. Printing a welcome message is such a task.

But most real tasks are not like that. Think of the light in a school corridor that is fitted with a light sensor. It does not switch on every time you walk past. It switches on only when the corridor is dark. The same walk gives two different results depending on one piece of information.

A ceiling fan with a temperature controller behaves the same way. If the room is above a set temperature, run faster. If it is below, run slower. The rule stays the same; the outcome changes.

Two questions a program keeps asking

Look closely at those two examples and you will find two separate needs.

The first is a choice. Should I do this at all? The corridor light has to check the darkness before switching on. A program that cannot make a choice can only ever do one fixed thing.

The second is repetition. Should I do this again? The bell striker in the chapter opener has to move six times, not once. Writing the same instruction six times would work, but writing it two hundred times would not.

The order in which the statements of a program actually run is called its control flow. Up to now your control flow has been a straight line. In this chapter you will learn to bend it — to branch it into two paths, and to bend it back on itself so a part runs again.

NoteDefinition

Control flow is the order in which the statements of a program are carried out.

You can see the difference in Fig. 15.1-1.

Diagram

Fig. 15.1-1 – two simple flow sketches side by side. Left, labelled “straight-line flow”: three boxes stacked with downward arrows, Start → Step 1 → Step 2 → Step 3 → Stop. Right, labelled “branching flow”: Start → Step 1 → a diamond labelled “Is it dark?” with a “yes” arrow to a box “switch light on” and a “no” arrow bypassing that box; both arrows join again and go to Stop

What a condition is

To choose a path, the program needs something it can test. That something is a condition — an expression that works out to either True or False, and nothing else.

You have already met the comparison operators that produce such values: >, <, >=, <=, == and !=. An expression like temperature > 30 is not a number. It is a plain yes-or-no answer written as True or False.

NoteDefinition

A condition is an expression whose value is either True or False.

A statement that uses a condition to pick which block of code runs is called a conditional statement. Sections 15.3 onward deal with these.

Q. Is temperature = 30 a condition?

A. No. A single = stores a value in a variable. A condition needs a comparison, so it would be temperature == 30, with two equal signs.

One honest limit

A condition can only answer with True or False. It cannot answer “almost”, “roughly”, or “it depends”. If you ask whether the temperature is above 30, then 30.1 is above and 30.0 is not, with nothing in between. The sharpness is useful when the rule really is sharp — a pass mark, a voting age, a battery level below which a robot must return to charge. It is a poor fit when the real-world idea is fuzzy, such as deciding whether a room feels warm. Programs handle fuzzy ideas by turning them into sharp cut-offs, and the person writing the program has to choose those cut-offs carefully.

15.2 Blocks and Indentation in Python

Before a program can choose a path, it needs a way to say which lines belong to that path. A choice is rarely one line long. “If the corridor is dark, switch on the light and note the time” is two instructions that must be treated as one group.

A group of lines that Python treats as a single unit is called a block.

NoteDefinition

A block is a group of statements that belong together and are carried out as one unit.

How Python marks a block

Many programming languages wrap a block in curly brackets. Python does not. It uses the blank space at the start of a line instead. Shifting a line to the right is called indentation, and in Python indentation is not decoration — it is what tells Python where a block begins and ends.

The rule has two parts.

First, the line that introduces a block ends with a colon. Second, every line of that block is indented by the same amount. The block ends at the first line that comes back out to the left. This is shown in Fig. 15.2-1.

Diagram

Fig. 15.2-1 – three lines of code drawn as plain bars. Top bar flush left, ending with a large colon, labelled “header line”. Below it, two bars shifted right by four spaces, with a tall square bracket beside them labelled “the block”. A fourth bar back at the far left, labelled “outside the block”. A small ruler mark between the left edge and the indented bars, labelled “4 spaces”

Python allows any consistent amount of indentation, but the accepted style is four spaces, and that is what this book uses everywhere. Most editors insert four spaces when you press the Tab key.

Q2. Why does Python care so much about the blank space at the start of a line?

A. Because in Python the indentation is the grouping. Other languages use brackets to show which lines belong to a block and treat spacing as a matter of neatness. Python has no such brackets, so the only way it can tell an inside line from an outside line is by how far the line is shifted to the right. Change the indentation and you change the meaning of the program.

Seeing indentation change the meaning

The short program below prints a result for a student. Two of its lines belong to a block and one does not.

printing a pass message Problem: A student passes if the marks are 40 or more. Print a message for a pass, and in every case print a closing line.

marks = 72

if marks >= 40:
    print("Pass")
    print("Well done")
print("Result printed")

Output:

Pass
Well done
Result printed

The first two print() lines are indented, so they form the block that belongs to the choice. The third is flush with the left margin, so it is outside the choice and runs whatever the marks are.

Now imagine shifting that third line four spaces to the right as well. It would join the block. With marks = 72 the output would look exactly the same, so you might think nothing had changed. But with marks = 30 the original program still prints Result printed, while the changed one prints nothing at all. Indentation is a real instruction, and a mistake in it does not always show up straight away.

mixing tab characters and spaces in the same block What goes wrong: A tab character and four spaces can look identical on the screen, but Python treats them as different. If one line of a block is indented with a tab and the next with spaces, Python stops with a TabError or an IndentationError, even though the code looks perfectly lined up. The fix: Pick spaces and use only spaces. Most editors have a setting that turns the Tab key into four spaces; switch it on once and the problem disappears.

Q. What happens if I forget the colon at the end of the header line?

A. Python stops before running anything and reports a SyntaxError. The colon is what announces that a block is coming, so without it Python cannot make sense of the next line.

Blocks inside blocks

A block can hold another block. The inner one is simply indented further — eight spaces instead of four, then twelve, and so on. You will see this in Section 15.3.4 and again in Section 15.4.4, where one choice sits inside another and one loop sits inside another.

Keep the nesting shallow where you can. Code that is indented five or six levels deep is hard to read and hard to correct, even for the person who wrote it.

15.3 Conditional Statements

You now have both pieces you need. A condition gives a True or False answer, and a block groups the lines that should run. A conditional statement joins the two: it tests a condition and runs a block only when the answer is True.

NoteDefinition

A conditional statement tests a condition and carries out a block of code depending on whether the condition is True or False.

Python’s conditional statements are built from three keywords — if, elif (short for “else if”) and else. Every conditional statement you write in this chapter is some arrangement of these three.

Three shapes of a decision

It helps to notice that not all decisions have the same shape.

Some decisions have one path. A message appears when a student has passed, and nothing happens otherwise. There is a block to run, or there is nothing to run.

Some have two paths. A student has either passed or failed, and a message is needed for both. Exactly one of the two blocks will run — never both, never neither.

Some have many paths. A grade might be A, B, C, D or F. Five paths, still exactly one of them taken. Fig. 15.3-1 shows the three shapes together.

Diagram

Fig. 15.3-1 – three small flowcharts in a row, each starting from a downward arrow. Left, labelled “one path”: a diamond with a “yes” arrow into a single box and a “no” arrow going straight past it; both join at the bottom. Middle, labelled “two paths”: a diamond with “yes” into a left box and “no” into a right box, both joining at the bottom. Right, labelled “many paths”: a vertical chain of three diamonds, each with a “yes” arrow going right into its own box and a “no” arrow going down to the next diamond, with a final box at the bottom labelled “none matched”; all boxes join at the bottom

The sections that follow take these one at a time — if for one path, if-else for two, and the if-elif-else ladder for many. After that come two more arrangements: one decision written inside another, and the match statement, which is a tidier way of writing certain many-path decisions.

Q. In a many-path decision, could two of the paths run?

A. Not in a single if-elif-else statement. Python checks the conditions from the top, runs the block belonging to the first one that is True, and then leaves the whole statement alone. The rest are never tested.

15.3.1 The if Statement

Start with the simplest kind of choice — the one-path decision.

A small wheeled robot built by a school club carries a sensor that measures how far away the nearest object is. The rule is short: if that object is closer than 20 cm, print a warning. If it is further away, say nothing and carry on. There is only one block of work here, and it may or may not be needed.

The method in words is short enough to write out first.

warning when an object is too close Input: the measured distance to the nearest object, in centimetres Output: a warning message, but only when the object is close Steps:

1. Take the measured distance.
2. Check whether the distance is less than 20.
3. If it is, print a warning.
4. Print a line saying the check is over.

Why it works: step 3 is reached only when the check in step 2 gives True, so the warning appears exactly when it is needed.

The general form

Python writes this with the if keyword. The if statement runs its block once when the condition is True, and skips the block completely when the condition is False.

if condition:
    # this block runs only when the condition is True

Three details matter. The word if comes first, the condition follows it, and the line ends with a colon. The block underneath is indented by four spaces, as you saw in Section 15.2.

NoteDefinition

An if statement runs a block of code once if its condition is True, and skips that block if the condition is False.

Here is the robot check written as a working program. The measured distance is stored in a variable first, so you can change one number and try the program again.

warning when an object is too close Problem: Print a warning only when the measured distance is less than 20 cm, then print a closing line in every case.

distance = 15    # distance to the nearest object, in centimetres

if distance < 20:
    print("Obstacle ahead")

print("Check complete")

Output:

Obstacle ahead
Check complete

With distance set to 15, the condition distance < 20 is True, so the warning is printed. Change the first line to distance = 45 and the condition becomes False. The indented line is skipped, and the only output is Check complete.

Q1. How does a program pick one path instead of another?

A. It works out a condition, which can only come to True or False, and uses that answer to decide whether to run the block of code attached to it. In an if statement the block runs when the answer is True and is skipped when it is False. Nothing else about the program changes — the same lines are there either way; only the path through them changes.

Q. Does the if block ever run more than once?

A. No. An if statement checks its condition once and runs the block at most once. Running a block again and again is the job of a loop, which Section 15.4 introduces.

writing = in place of == What goes wrong: if distance = 20: looks reasonable but stops the program with a SyntaxError. A single = is the assignment operator — it stores a value in a variable, which is not a question and has no True or False answer. The fix: Use == to compare and = to assign. Read == aloud as “is equal to” and = as “gets”, and the two stop blurring together.

One limit worth knowing

An if statement on its own can say what to do when the condition holds, but it has no place to put the work needed when the condition does not hold. For the robot that was fine — nothing had to happen. When both outcomes need an action, you need the two-path form in Section 15.3.2.

15.3.2 The if-else Statement

Many decisions need an action on both sides.

A school has an overhead water tank with a level sensor. When the water level falls below 30 per cent of full, the pump must be switched on. When it is at or above 30 per cent, the pump must be switched off. Neither outcome is “do nothing” — something has to happen either way.

An if statement alone cannot express this neatly. You could write two separate if statements, one for each case, but then Python would test the same fact twice and you would have to keep the two conditions in step by hand. Python offers a better tool.

The general form

The if-else statement attaches a second block to the first. Python tests the condition once. If it is True, the if block runs. If it is False, the else block runs instead. Exactly one of the two blocks runs — never both, and never neither.

if condition:
    # this block runs when the condition is True
else:
    # this block runs when the condition is False

Notice that else sits at the same indentation as if, and it too ends with a colon. It carries no condition of its own, because it simply catches everything the if did not.

NoteDefinition

An if-else statement runs one block when its condition is True and a different block when the condition is False.

Here is the tank rule as a program.

switching a tank pump on or off Problem: Report that the pump should be on when the water level is below 30 per cent, and off otherwise.

level = 22    # water level in the tank, as a percentage of full

if level < 30:
    print("Level low - pump ON")
else:
    print("Level fine - pump OFF")

Output:

Level low - pump ON

With level set to 22 the condition is True, so only the first message appears. Set level to 30 and the condition 30 < 30 is False, so the second message appears instead. A level of exactly 30 counts as fine, because the condition asks for less than 30 and not less than or equal to. Where you place that boundary is your decision as the programmer, and it should match the rule the school actually wants.

A real tank pump runs on mains electricity, which is never something to wire up yourself. In a school lab, switch a low-voltage model pump through a relay module and let a teacher handle any mains connection. Disconnect the power supply before changing any wiring.

Q. Can an else be written without an if above it?

A. No. else has no condition of its own, so on its own it has nothing to be the alternative to. Python reports a SyntaxError.

Comparing the two forms

Table 15.1 sets the two statements side by side.

Table 15.1 – if vs if-else | Feature | if | if-else | |—|—|—| | Number of blocks | One | Two | | When the condition is True | The block runs | The if block runs | | When the condition is False | Nothing runs | The else block runs | | Blocks that can run at one time | At most one | Exactly one | | Best used when | Only one outcome needs an action | Both outcomes need an action |

putting a condition after else What goes wrong: Writing else level >= 30: looks tidy and symmetrical, but Python rejects it. The else keyword must be followed straight away by the colon. The fix: If the second branch really does need a condition of its own, that is a different statement — the elif ladder in Section 15.3.3. If it does not, drop the condition and leave else: alone.

One limit worth knowing

if-else splits the work into exactly two paths. The moment a problem has three or more outcomes — a pump that runs fast, runs slow, or stays off — two blocks are not enough, and you need the form in the next section.

15.3.3 The if-elif-else Ladder

A city bus charges by distance. Up to 5 km the fare is ₹10. Beyond that and up to 10 km it is ₹15. Beyond that and up to 20 km it is ₹25. Anything longer costs ₹40.

That is four outcomes, and only one of them can apply to a single journey. Two blocks are not enough.

You could write four separate if statements. It would work, but it would be clumsy: Python would test all four conditions even after the right one had been found, and every condition would have to spell out both its lower and upper limit. Python offers a form built exactly for this shape.

The general form

The elif statement — short for “else if” — lets you hang extra conditions under an if. Together they form what is usually called an if-elif-else ladder.

if condition1:
    # runs when condition1 is True
elif condition2:
    # runs when condition1 is False and condition2 is True
elif condition3:
    # runs when the two above are False and condition3 is True
else:
    # runs when none of the conditions above is True

Python works down the ladder from the top. The moment a condition comes out True, its block runs and the whole statement finishes — the conditions below it are never even looked at. If none is True, the else block runs.

NoteDefinition

An if-elif-else ladder tests conditions in order from the top and runs the block belonging to the first condition that is True.

You may write as many elif branches as the problem needs. The final else is optional; leaving it out simply means that when nothing matches, nothing runs.

Q3. When there are many conditions to check, what is the neatest way to write them?

A. Put them in an if-elif-else ladder. Each elif carries its own condition, Python tests them in order and stops at the first one that is True, and a final else catches everything left over. This is neater than a pile of separate if statements because the branches are visibly linked, exactly one of them can run, and Python does no work testing conditions after it has already found its answer.

The bus fare as a program

finding a bus fare from the distance Problem: Print the fare for a journey, using the four slabs ₹10, ₹15, ₹25 and ₹40.

distance = 12    # journey length in kilometres

if distance <= 5:
    fare = 10
elif distance <= 10:
    fare = 15
elif distance <= 20:
    fare = 25
else:
    fare = 40

print("Fare in rupees:", fare)

Output:

Fare in rupees: 25

Follow what happened. The first condition asks whether 12 is at most 5 — False, so the block is skipped. The second asks whether 12 is at most 10 — also False. The third asks whether 12 is at most 20 — True, so fare becomes 25 and Python leaves the ladder. The else branch is never reached.

Notice that the third condition did not have to say “more than 10 and at most 20”. It only had to rule out 20 and above, because a journey of 10 km or less would already have been caught higher up. This is what makes the ladder shorter than four separate if statements.

monthly bus cost for a student

Given: one-way distance = 12 km, fare for that slab = ₹25, two journeys a day, 24 school days in the month

Find: the total bus cost for the month

Working:

Cost for one day = ₹25 × 2 = ₹50

Cost for the month = ₹50 × 24 = ₹1200

₹1200

putting the widest condition first What goes wrong: If the ladder starts with if distance <= 20:, then a 3 km journey satisfies it straight away and is charged ₹25. The narrower branches below are never reached, so three of the four slabs become unreachable. The fix: Order the branches from the narrowest condition to the widest, so each one only has to rule out what the branches above it have already handled.

Q. What happens if no condition is True and there is no else?

A. The whole statement is skipped and the program carries on with the next line. Nothing goes wrong, but any variable the branches were meant to set will still be unset — which is why a ladder that produces a value usually needs an else.

Change distance in the program to 5, then to 10, then to 20, and predict the fare each time before you run it. These are the exact boundary values, and they are where slab rules most often go wrong.

15.3.4 Nested if Statements

Sometimes a second question only makes sense once the first one has been answered.

Think about the wheeled robot from Section 15.3.1 again. Before it moves anywhere, it checks its battery. If the charge is too low, it must go back to the charging point, and nothing else matters — there is no point asking about the path ahead, because the robot is not going that way. Only when the charge is healthy does the second question arise: is the way in front clear?

That is a decision inside a decision. Writing one conditional statement inside the block of another is called nesting.

NoteDefinition

A nested if is an if statement written inside the block of another if statement, so that the inner condition is only tested when the outer one has already been satisfied.

Fig. 15.3.4-1 shows the shape of the robot’s decision.

Diagram

Fig. 15.3.4-1 – flowchart. Start at top, arrow into a diamond labelled “battery at least 20%?”. The “no” arrow goes right into a box “return to charger”. The “yes” arrow goes down into a second diamond labelled “clearance more than 20 cm?”, drawn inside a dotted rectangle labelled “runs only when the battery is fine”. From this diamond, “yes” leads to a box “move forward” and “no” to a box “stop and turn”. All three action boxes join at the bottom and go to Stop

Writing it in Python

The inner statement is an ordinary if or if-else. The only new thing is the indentation: because it sits inside the outer block, its own header line is indented four spaces, and its blocks are indented eight.

robot decision using a nested if Problem: Send the robot to the charger when its battery is below 20 per cent. Otherwise move it forward if the way ahead is clear, and make it stop and turn if it is not.

battery = 65      # remaining charge, as a percentage of full
clearance = 12    # distance to the nearest object ahead, in centimetres

if battery >= 20:
    if clearance > 20:
        print("Path clear - move forward")
    else:
        print("Object close - stop and turn")
else:
    print("Battery low - return to charger")

Output:

Object close - stop and turn

Read it from the outside in. The battery is 65, so the outer condition is True and Python enters the outer block. Inside, the clearance of 12 cm is not more than 20, so the inner else runs. Change battery to 8 and the outer condition becomes False — the whole inner statement is skipped without ever being tested, and only the charger message is printed.

Q. How do I tell which else belongs to which if?

A. By its indentation. An else belongs to the nearest if above it that is indented by the same amount. In the program above, the first else is indented four spaces, so it pairs with the inner if; the second is flush left, so it pairs with the outer one.

Nesting or a ladder?

Beginners often use a nested if where an if-elif-else ladder would read better, and the two are easy to confuse. Table 15.2 separates them.

Table 15.2 – nested if vs if-elif-else ladder | Feature | Nested if | if-elif-else ladder | |—|—|—| | What the conditions test | Different facts, one after the other | The same fact, split into ranges or cases | | When the inner or later condition is tested | Only after the outer one is True | Only after the earlier ones are False | | Indentation | Grows deeper with every level | Stays flat | | Good fit | Battery first, then path ahead | Fare slabs by distance |

The useful test is to ask whether the second question would still make sense if the first were answered the other way. If it would not, you need nesting. If it would, a ladder is almost always clearer.

nesting more deeply than the problem needs What goes wrong: A four- or five-level stack of if statements pushes the code far to the right and makes it very hard to see which branch handles which case. Most such stacks are really one fact split into ranges, which is a ladder wearing the wrong clothes. The fix: Before adding a level, check whether the new condition depends on the one above it. If it does not, turn the stack into a ladder, or combine two conditions into one using and.

Two levels of nesting are common and readable. Beyond three, it is worth stopping to rework the logic.

15.3.5 Case and Switch: the match Statement

Some many-path decisions are not about ranges at all. They are about a single value matching one of a fixed set of choices.

A robot receives one-letter commands from a control app: F for forward, B for back, L for left, R for right. Anything else is a mistake and should be reported. There are no slabs and no boundaries here — just five separate cases, each compared against the same variable.

An if-elif-else ladder handles this perfectly well, but the variable name has to be repeated in every branch: if command == "F":, then elif command == "B":, and so on. Many programming languages provide a tidier form for exactly this shape, usually called a switch statement with a list of cases inside it.

Python had no such statement for most of its life. Programmers used if-elif-else ladders instead. The match statement was added in Python version 3.10, released in 2021, so it will not run on older versions of Python.

The general form

Python’s version is written with the keywords match and case. The match statement compares one value against a list of cases and runs the block belonging to the first case that matches.

match value:
    case pattern1:
        # runs when the value matches pattern1
    case pattern2:
        # runs when the value matches pattern2
    case _:
        # runs when none of the cases above matched

The match line ends with a colon, and each case line does too. The single underscore, _, is a wildcard that matches anything, so case _ plays the same part as else in a ladder.

NoteDefinition

A match statement compares a value against a list of cases and runs the block belonging to the first case that matches.

reading a robot command letter Problem: Print the movement that a one-letter command stands for, and report anything unrecognised.

command = "L"

match command:
    case "F":
        print("Move forward")
    case "B":
        print("Move back")
    case "L":
        print("Turn left")
    case "R":
        print("Turn right")
    case _:
        print("Unknown command")

Output:

Turn left

Python compares command with "F", then "B", then "L", finds the match, prints the message and leaves the statement. The remaining cases are not tested.

Two cases can share a block by joining their patterns with a vertical bar, so that both a capital and a small letter are accepted:

    case "F" | "f":
        print("Move forward")

Q. Does each case need a break at the end, as in some other languages?

A. No. In a few languages a case runs on into the next one unless you stop it. Python’s match runs exactly one block and then ends, so there is nothing to break out of.

writing a plain name instead of _ for the last case What goes wrong: case other: looks like a sensible name for the leftover case, but a bare name in a case line does not compare anything. It matches every value and stores it in a variable called other, so that case swallows everything and the cases below it are unreachable. The fix: Use case _: for the leftover case. If you need the value, it is already in the variable you matched on.

When to use which

Table 15.3 compares the two many-path forms.

Table 15.3 – match statement vs if-elif-else ladder | Feature | match | if-elif-else | |—|—|—| | What the branches compare | One value against fixed choices | Any condition, different in each branch | | Suits ranges such as marks or distance | No | Yes | | Catch-all branch | case _ | else | | Python version needed | 3.10 or later | Any version |

For a fixed list of values — command letters, menu numbers, day names — match reads more clearly. For ranges and for conditions that test different things, the ladder is the right tool. Neither can do anything the other cannot; the choice is about which one a reader will understand faster.

15.3.6 Shorthand Conditional Statements

Some decisions are so small that spreading them over four lines makes them harder to read, not easier. Python offers two short forms for these. Both are conveniences — neither can do anything the full forms in the earlier sections cannot.

An if statement on one line

When the block under an if is a single short statement, it may be written on the same line, straight after the colon.

marks = 91
if marks > 90: print("Distinction")

Output:

Distinction

This is the same if statement you already know, just written more compactly. It is worth using only when the whole line stays short and obvious. As soon as there is more than one statement to run, the block must go back onto its own indented lines.

The conditional expression

The second short form is more useful. Often an if-else exists only to put one value or another into a variable:

if temperature > 40:
    advice = "Stay indoors"
else:
    advice = "Fine to go out"

Four lines, and the variable name is written twice, for what is really a single choice between two values. Python lets you write that choice as one expression.

A conditional expression picks between two values in a single line. It is sometimes called the ternary operator, because it works on three parts: the two values and the condition between them.

value_if_true if condition else value_if_false

Read it in the order it is written: this value, if the condition holds, otherwise that value. The condition sits in the middle, which takes a little getting used to.

NoteDefinition

A conditional expression gives one of two values, chosen by a condition, and can be used anywhere a value is expected.

heat advice using a conditional expression Problem: Store one of two pieces of advice in a variable, depending on the day’s temperature, and print it.

temperature = 34    # today's highest temperature, in degrees Celsius

advice = "Stay indoors" if temperature > 40 else "Fine to go out"
print(advice)

Output:

Fine to go out

The condition temperature > 40 is False, so the expression takes the value after else and stores it in advice. The four-line version above and this one behave identically.

Q. Can the else part be left out?

A. No. A conditional expression must produce a value, and without an else there would be nothing to produce when the condition is False. Python reports a SyntaxError. An ordinary if statement has no such rule, because it produces no value.

putting a whole statement inside a conditional expression What goes wrong: Writing print("Hot") if temperature > 40 else advice = "cool" fails, because an assignment is a statement and not a value. A conditional expression can only choose between two values. The fix: If either branch has to do something rather than be something, use a full if-else statement.

Knowing when not to use it

Shorthand is a good servant and a poor master. A single, short conditional expression is easy to read. One that stretches past the width of the screen, or one written inside another, is much harder to follow than the plain if-else it replaced — and code that is hard to follow is code that hides mistakes.

Use the short forms for small, obvious choices. Everywhere else, the full form is the better piece of writing.

15.4 Loops: Doing the Same Work Again

The school bell in the chapter opener has to strike six times. Written as straight-line code, that is six identical lines:

print("beat")
print("beat")
print("beat")
print("beat")
print("beat")
print("beat")

It works. But if the school later decides on eight beats, six lines have to become eight. If a program had to print a message for each of 600 students, nobody would type 600 lines. And if the striker had to keep moving until a sensor said the bell had stopped ringing, the number of lines could not be worked out in advance at all.

Repetition needs its own kind of statement.

What a loop is

A loop is a statement that runs a block of code again and again. Each single run of the block is called one iteration.

NoteDefinition

A loop is a statement that repeats a block of code. One run through that block is called an iteration.

The idea is not new to you. A teacher marking a set of answer papers does the same few steps for every paper: read, mark, write the total, put it on the pile. The steps do not change; only the paper does. That is exactly what a loop does — it holds the steps still and lets the data move through them.

Fig. 15.4-1 shows the shape.

Diagram

Fig. 15.4-1 – flowchart of a loop. Start at top, arrow down into a diamond labelled “should the block run again?”. The “no” arrow leaves to the right and goes to Stop. The “yes” arrow goes down into a box labelled “run the block once”, and from the bottom of that box a curved arrow travels back up to the left side of the diamond, labelled “repeat”

Notice where the test sits. The question is asked before each iteration, not after. If the answer is no the very first time, the block does not run even once.

Two ways of saying “again”

Look back at the three situations at the start of this section and you will find two different kinds of repetition hiding in them.

In the first, the number of repeats is known before the loop starts. Six beats. Six hundred students. Every letter in a word. You could count them out in advance.

In the second, the number is not known. Keep asking for a password until the right one is typed. Keep the striker moving until the sensor reports silence. The loop stops when something changes, and there is no way to say beforehand how many rounds that will take.

Python has a loop for each. The while loop in Section 15.4.1 repeats as long as a condition stays True, which fits the second kind. The for loop in Section 15.4.2 walks through a collection of items one at a time, which fits the first. Section 15.6 compares them properly, once you have used both.

Q. Can a loop hold a conditional statement inside it?

A. Yes, and it very often does. An if inside a loop lets the program treat some iterations differently from others — printing a message only for the students who scored above a mark, for instance.

The mistake that never ends

Every loop needs some way of eventually stopping. A loop whose stopping condition never becomes False is called an infinite loop, and it will keep running until you force the program to close. Press Ctrl and C together in the terminal to stop a program that has run away.

Infinite loops are the commonest beginner’s mistake with while, and Section 15.4.1 shows exactly how they happen.

15.4.1 The while Loop

A pump is filling the school’s overhead tank. Nobody can say in advance how many minutes it will take, because that depends on how empty the tank was when the pump started. What can be said is when to stop: keep pumping while the level is below full.

That is the shape the while loop was made for. It repeats a block for as long as a condition stays True.

NoteDefinition

A while loop repeats a block of code as long as its condition is True, and stops as soon as the condition becomes False.

The general form
while condition:
    # this block runs again and again while the condition is True

The line looks almost exactly like an if. The difference is what happens at the end of the block: an if moves on, while a while goes back to the top and tests the condition again.

Python checks the condition before every iteration, including the first. So if the condition is already False when the loop is reached, the block never runs at all.

Q4. How can a program repeat a job when nobody knows in advance how many times it must run?

A. With a while loop. Instead of being told a number of repeats, the loop is given a condition. Before each iteration it checks that condition, runs the block if the answer is True, and stops the moment the answer is False. The count is never stated anywhere — it simply turns out to be however many rounds it took for the condition to change.

Three parts that must all be there

Nearly every while loop has three moving parts, and leaving out any one of them breaks it.

First, something must be set up before the loop starts — a starting level, a starting count. Second, the condition must be built from something that can change. Third, the block must actually change it, or the condition will stay True for ever.

The method for the tank is written out below.

filling a tank until it is full Input: the water level at the start, as a percentage of full Output: the level after each minute, and a message when the tank is full Steps:

1. Note the starting level and set the minute count to 0.
2. While the level is below 100:
   a. Add the amount the pump delivers in one minute to the level.
   b. Add 1 to the minute count.
   c. Print the minute count and the new level.
3. Print that the tank is full.

Why it works: step 2a changes the very quantity the condition tests, so the level climbs towards 100 and the loop is certain to end.

filling a tank until it is full Problem: Starting from a level of 40 per cent and adding 15 per cent each minute, print the level after every minute until the tank is full.

level = 40      # water level as a percentage of full
minutes = 0

while level < 100:
    level = level + 15
    minutes = minutes + 1
    print("After", minutes, "minute(s) the level is", level, "%")

print("Tank full - pump off")

Output:

After 1 minute(s) the level is 55 %
After 2 minute(s) the level is 70 %
After 3 minute(s) the level is 85 %
After 4 minute(s) the level is 100 %
Tank full - pump off

Table 15.4 lists what the two variables hold at the end of each iteration. Reading down the last column shows why the loop stopped when it did.

Table 15.4 – the two variables at the end of each iteration | Iteration | minutes | level | Is level < 100 at the next test? | |—|—|—|—| | 1 | 1 | 55 | Yes | | 2 | 2 | 70 | Yes | | 3 | 3 | 85 | Yes | | 4 | 4 | 100 | No — loop ends |

The final message is printed once, after the loop, because it is not indented into the block.

Q. Why does the loop stop at exactly 100 and not overshoot?

A. Here the numbers happen to land on 100 exactly. If the pump added 20 per cent a minute instead, the level would jump from 80 to 120 and the loop would stop there — the condition is tested between iterations, not in the middle of one.

forgetting to change the variable inside the loop What goes wrong: Leaving out the line level = level + 15 means the level stays 40 for ever, 40 < 100 is always True, and the loop never ends. The program appears to freeze while printing endlessly. The fix: Before running a while loop, find the line inside the block that moves the condition towards False. If there is no such line, the loop is an infinite loop. If a program does run away, press Ctrl and C together to stop it.

Change the first line to level = 100 and predict the output before running the program. Then explain in one sentence why the loop body never runs.

15.4.2 The for Loop

Go back to the school bell. Six beats, every time. The count is fixed and known before anything starts.

A while loop could do it — set a counter to 1, keep going while the counter is at most 6, add 1 each round. But that means writing three separate things and keeping them in step, when all you really wanted to say was “do this six times”. Python has a loop that says exactly that.

The for loop walks through a collection of items, one at a time, running its block once for each item.

NoteDefinition

A for loop runs its block once for every item in a collection, taking the items one after another.

The general form
for variable in collection:
    # this block runs once for each item in the collection

The name written after for is the loop variable. Python sets it to the first item, runs the block, sets it to the second item, runs the block again, and so on. When the items run out, the loop ends by itself. You never have to write a condition, and you never have to remember to increase a counter — that is the whole point of the for loop.

Anything a for loop can walk through is called an iterable: an object able to hand over its items one at a time. Lists, tuples, strings, dictionaries and ranges are all iterables, and Section 15.4.3 takes them one by one.

Counting with range()

For plain counting there is a built-in tool called range(), which produces a run of whole numbers without your having to type them out.

range() can be given one, two or three numbers, as Table 15.5 shows.

Table 15.5 – the numbers produced by different forms of range() | Written as | Meaning | Numbers produced | |—|—|—| | range(5) | count from 0, stop before 5 | 0, 1, 2, 3, 4 | | range(2, 6) | count from 2, stop before 6 | 2, 3, 4, 5 | | range(0, 10, 2) | from 0, stop before 10, in steps of 2 | 0, 2, 4, 6, 8 | | range(5, 0, -1) | from 5, stop before 0, counting down | 5, 4, 3, 2, 1 |

One rule runs through the whole table: the stopping number is never included. range(5) gives five numbers but never reaches 5. This catches almost everybody once, so it is worth saying twice — the first number is included, the last is not.

striking the school bell six times Problem: Print one line for each of the six beats of the bell, numbered 1 to 6, and a closing line at the end.

for beat in range(1, 7):
    print("beat", beat)

print("bell finished")

Output:

beat 1
beat 2
beat 3
beat 4
beat 5
beat 6
bell finished

The range starts at 1 and stops before 7, so the loop variable beat takes the values 1 to 6 — six iterations, one line each. If the school later wants eight beats, one number changes and nothing else does.

Q. What is the loop variable holding after the loop has finished?

A. The last value it was given — 6 in this program. It is an ordinary variable and it survives the loop, but relying on that value afterwards makes a program confusing, so it is better to store anything you still need in a variable of its own.

getting the count wrong by one What goes wrong: range(1, 6) looks like “1 to 6” and is expected to give six beats, but it stops before 6 and gives only five. range(6) starts at 0 and also gives five beats numbered 0 to 5. The fix: To count from 1 up to and including n, write range(1, n + 1). To count n times when the numbers themselves do not matter, range(n) is simplest.

Where the two loops sit

A for loop knows how many iterations it will run before it starts, because the collection is already there to be counted. A while loop does not, because it only ever looks one condition-test ahead. That is the real difference between them, and Section 15.6 returns to it.

15.4.3 Looping Over Lists, Tuples, Strings and Dictionaries

range() is useful, but the collections you met in Chapter 13 are far more interesting to walk through. A for loop treats them all the same way: hand me your items one at a time, and I will run my block for each.

This section takes the four you know — list, tuple, string and dictionary — and shows what the loop variable holds in each case.

Looping over a list

A list holds items in order, and can be changed after it is made. Looping over it gives you the items themselves, not their positions.

Here a robot has stored five distance readings taken as it moved along a corridor.

checking a list of distance readings Problem: Print each stored reading and say whether it is safe or too close, using a limit of 20 cm.

readings = [45, 32, 18, 27, 9]

for reading in readings:
    if reading < 20:
        print(reading, "cm - too close")
    else:
        print(reading, "cm - safe")

Output:

45 cm - safe
32 cm - safe
18 cm - too close
27 cm - safe
9 cm - too close

The loop variable reading is 45 in the first iteration, 32 in the second, and so on. Notice the if-else sitting inside the loop: the same test is applied to every item, but the answer differs from one item to the next. Conditions inside loops are the commonest and most useful combination in the whole of programming.

Looping over a tuple

A tuple also holds items in order, but once made it cannot be changed. That makes it the right choice for a fixed list that should not be edited by accident — the joints of a robot arm, for instance.

naming the joints of a robot arm Problem: Print a checking message for each joint in a fixed list of four.

joints = ("base", "shoulder", "elbow", "wrist")

for joint in joints:
    print("Checking", joint)

Output:

Checking base
Checking shoulder
Checking elbow
Checking wrist

As far as the loop is concerned, nothing has changed from the list. Round brackets in place of square ones is the only visible difference.

Looping over a string

A string is a sequence of characters, so a for loop hands over one character at a time — letters, digits, punctuation and spaces alike.

counting one letter in a word Problem: Count how many times the letter o appears in a word, ignoring whether it is capital or small.

word = "Robotics"
count = 0

for letter in word:
    if letter.lower() == "o":
        count = count + 1

print("The letter o appears", count, "times")

Output:

The letter o appears 2 times

letter.lower() gives back the small-letter version of the character, so a capital O would be counted too. The variable count has to be set to 0 before the loop, because it must survive from one iteration to the next.

Q. Why can the counter not be set to 0 inside the loop?

A. Because then it would be reset at the start of every iteration, and the final answer would only ever be 0 or 1. Anything that has to accumulate across iterations must be set up before the loop begins.

Looping over a dictionary

A dictionary stores pairs — a key and the value that goes with it. Looping over the dictionary itself gives you the keys, and you can use a key to look up its value. Looping over .items() gives you both at once, which is usually neater.

reading a dictionary of subject scores Problem: Print the subject names on their own, then print each subject with its score.

scores = {"Maths": 78, "Science": 85, "English": 72}

for subject in scores:
    print(subject)

print("---")

for subject, score in scores.items():
    print(subject, "scored", score)

Output:

Maths
Science
English
***
Maths scored 78
Science scored 85
English scored 72

The second loop has two names after for, separated by a comma, because .items() hands over a pair each time. Python fills the first name with the key and the second with the value.

From Python 3.7 onwards a dictionary keeps its items in the order they were added, so the loop visits them in that order. Older versions gave no such promise.

expecting the loop variable to be a position number What goes wrong: Writing print(readings[reading]) inside the list loop fails, because reading is already an item such as 45, not a position such as 0. Python reports an IndexError. The fix: Use the loop variable directly. If you truly need positions as well, loop over range(len(readings)) and use the number to look items up.

The dictionary above holds one student’s scores, and a loop makes it trivial to print, copy or share every value in it. Marks, attendance and health records are personal information. Use made-up names and made-up numbers in classwork, keep real records to the people who are entitled to see them, and never load a real class list into a practice program.

15.4.4 Nested Loops

A school hall is being labelled for an examination. There are 3 rows of seats, and each row has 4 seats. Every seat needs a label such as R2S3, meaning row 2, seat 3.

One loop is not enough here. Walking through the rows gives you 3 iterations; walking through the seats gives you 4. What you actually need is every seat in every row — 4 seats, repeated for each of the 3 rows.

That is a loop inside a loop, and it is called a nested loop.

NoteDefinition

A nested loop is a loop written inside the block of another loop. The inner loop runs all the way through once for every single iteration of the outer loop.

The second sentence of that definition is the whole idea. Fig. 15.4.4-1 shows it.

Diagram

Fig. 15.4.4-1 – a large rounded rectangle labelled “outer loop: rows 1 to 3” containing a smaller rounded rectangle labelled “inner loop: seats 1 to 4”, which in turn contains a small box labelled “print one seat label”. A curved arrow inside the small rectangle loops back on itself labelled “4 times”, and a larger curved arrow inside the big rectangle loops back on itself labelled “3 times”. To the right, a short note reads “3 × 4 = 12 labels”

Writing it in Python

The inner loop is an ordinary for loop. It simply lives inside the outer loop’s block, indented one more level.

labelling the seats in an examination hall Problem: Print a label for every seat in a hall of 3 rows with 4 seats in each row, one row of labels per line.

for row in range(1, 4):
    for seat in range(1, 5):
        print("R" + str(row) + "S" + str(seat), end=" ")
    print()

Output:

R1S1 R1S2 R1S3 R1S4
R2S1 R2S2 R2S3 R2S4
R3S1 R3S2 R3S3 R3S4

Two small details do the tidying. Normally print() moves to a new line when it finishes; end=" " tells it to put a space there instead, so the four labels of a row stay side by side. The bare print() on the last line has nothing to print, so all it does is move to the next line — and because it is indented at the outer level, it runs once per row, after the inner loop has finished.

Follow the order of events. The outer loop sets row to 1 and enters its block. The inner loop then runs completely, giving seats 1, 2, 3 and 4. Only then does print() end the line, and only then does the outer loop set row to 2 and start the inner loop all over again.

how many labels are printed

Given: 3 rows, 4 seats in each row

Find: the total number of labels printed

Working:

The inner loop runs 4 times for each single iteration of the outer loop.

The outer loop runs 3 times.

Total = 3 × 4 = 12

12 labels

Q. Which loop variable changes faster?

A. The inner one. seat runs through all four of its values before row changes even once. In general, the innermost loop variable is always the one that moves quickest.

using the same name for both loop variables What goes wrong: Writing for row in range(1, 4): and then for row in range(1, 5): inside it means the inner loop overwrites the outer loop’s variable. The program still runs, but every label comes out with the wrong row number, and nothing warns you. The fix: Give each loop its own variable name. Names that say what the loop is walking through — row and seat — also make the code easier to read.

The cost of nesting

Nested loops multiply. Three rows and four seats is 12 iterations, which is nothing. But a nested loop over a list of 1000 items inside another loop over the same 1000 items is a million iterations, and that is enough to make a program noticeably slow.

Two levels are common and perfectly reasonable. Beyond that, it is worth asking whether the work can be done another way.

15.5 Changing the Flow of a Loop

So far every loop you have written has been all-or-nothing. It starts, it runs its block for every item or until its condition fails, and then it ends. The block itself has had no say in the matter.

Real tasks are not always so patient.

A robot walking through a list of stored readings may find an obstacle at the second reading. There is no sense in checking the remaining three — the answer is already known, and carrying on wastes time.

A program totalling a set of readings may come across one that is clearly faulty, perhaps a negative distance. It should ignore that single reading and carry straight on with the rest, not abandon the whole job.

And sometimes a block is needed that does nothing at all — a branch you have decided to leave empty for now.

Python has one small keyword for each of these three situations: break, continue and pass. They are usually called loop control statements, because they change the normal flow of a loop from inside it.

NoteDefinition

Loop control statements change the ordinary flow of a loop from within its own block.

Fig. 15.5-1 shows where each one sends the program.

Diagram

Fig. 15.5-1 – a tall rounded rectangle labelled “the loop”, with a diamond “condition?” at the top and a block of three stacked statement bars below it, and a return arrow from the bottom of the block back up to the diamond. Three coloured arrows leave the middle bar. One, labelled “break”, exits the rectangle entirely and points to a box outside marked “next statement after the loop”. One, labelled “continue”, curves back up to the diamond, skipping the bars below it. One, labelled “pass”, is drawn as a very short arrow to the very next bar, with the note “does nothing”

Q5. Can a loop be stopped halfway, or made to skip just one round?

A. Both, and they are two different keywords. break stops the loop completely and moves on to whatever comes after it, so the remaining iterations never happen. continue abandons only the current iteration — the rest of the block is skipped, but the loop carries on with its next round as usual. A third keyword, pass, does neither; it is a placeholder that fills a block which has to exist but has nothing in it yet.

The three sections that follow take them one at a time. All three work in a while loop and in a for loop, in exactly the same way.

15.5.1 The break Statement

A robot has stored five distance readings taken at five points along a corridor. It has to report the first point where something is closer than 20 cm.

The word first changes everything. Once that point is found, the remaining readings tell you nothing you need. Checking them anyway is not wrong, but it is wasted work — and in a program that searches a list of ten thousand items, wasted work is worth avoiding.

The break statement ends a loop immediately. The rest of the current iteration is abandoned, no further iterations happen, and the program carries on with the first statement after the loop.

NoteDefinition

break stops a loop at once and moves the program on to the statement following the loop.

Searching until something is found

finding the first close reading Input: a list of distance readings, in centimetres Output: the position of the first reading below 20 cm Steps:

1. Set the step count to 0.
2. For each reading in the list:
   a. Add 1 to the step count.
   b. If the reading is below 20, report the step count and stop the loop.
   c. Otherwise report that the step is clear.
3. Print that the scan is over.

Why it works: step 2b leaves the loop the moment the answer is known, so no reading after the first close one is ever examined.

finding the first close reading Problem: Report the first point in a corridor scan where an object is closer than 20 cm, and check no further.

readings = [52, 41, 12, 33, 8]
step = 0

for reading in readings:
    step = step + 1
    if reading < 20:
        print("Obstacle found at step", step)
        break
    print("Step", step, "clear")

print("Scan over")

Output:

Step 1 clear
Step 2 clear
Obstacle found at step 3
Scan over

The list holds five readings, but the loop ran only three times. At step 3 the condition became True, the message was printed, and break sent the program straight out of the loop. The readings 33 and 8 were never looked at — and note that 8 is closer still, which is exactly the point: the program was asked for the first close reading, not the closest one.

Q. What happens if no reading is below 20?

A. The break is never reached, so the loop simply runs through all five readings and ends on its own. break changes what happens only when its condition is met.

One loop at a time

In a nested loop, break leaves only the loop it is written in — the innermost one. The loop outside it carries on to its next iteration as though nothing had happened.

expecting one break to escape both loops What goes wrong: In a nested loop, a break in the inner block ends the inner loop only. The outer loop then starts the inner loop again from the beginning, so the program appears to ignore the break completely. The fix: Set a variable such as found = True before breaking out of the inner loop, and test that variable in the outer loop so it can stop too.

When not to reach for it

break is easy to overuse. A loop with three or four break statements scattered through it becomes hard to read, because there is no longer one clear place where the loop ends.

Where the loop’s own condition can express the stopping rule, let it. break earns its place when the rule can only be discovered inside the block — as with the search above, where you cannot know which reading is the first close one until you look at it.

15.5.2 The continue Statement

Sensors are not perfect. A distance sensor that receives no echo often stores a value that could never be real — a negative distance, say. Such a value is not a measurement at all, and adding it to a total would drag the answer away from the truth.

The job here is different from the search in Section 15.5.1. Nothing has been found and the loop should not stop. One item simply has to be left out, and the rest handled as usual.

The continue statement abandons the current iteration only. Everything below it in the block is skipped, and the loop moves straight on to its next round.

NoteDefinition

continue skips the rest of the current iteration and starts the next one, leaving the loop itself running.

Ignoring the faulty readings

averaging readings while ignoring faulty ones Problem: Add up a list of distance readings and find their average, leaving out any negative value, and report how many faulty readings were skipped.

readings = [34, -1, 27, -1, 45, 30]
total = 0
good = 0

for reading in readings:
    if reading < 0:
        print("Faulty reading skipped")
        continue
    total = total + reading
    good = good + 1

print("Readings used:", good)
print("Total:", total)
print("Average:", total / good)

Output:

Faulty reading skipped
Faulty reading skipped
Readings used: 4
Total: 136
Average: 34.0

The loop ran all six times — continue never shortens a loop. On the second and fourth iterations it jumped past the two adding lines, so those readings changed neither total nor good. The other four were counted normally.

The average is the total divided by how many values went into it, which is why good had to be counted separately. Dividing by 6 would have treated the two rejected readings as though they were zeros.

\(\text{average} = \dfrac{\text{total of the good readings}}{\text{number of good readings}}\)

the average of the good readings

Given: the readings kept are 34 cm, 27 cm, 45 cm and 30 cm

Find: their average

Working:

Total = 34 + 27 + 45 + 30 = 136 cm

Number of readings = 4

Average = 136 ÷ 4 = 34 cm

34 cm

This matches the 34.0 the program printed. Python shows 34.0 rather than 34 because dividing with / always produces a float.

Q. Could an if-else have done this without continue?

A. Yes. Putting the adding lines inside an else branch gives exactly the same result. continue is preferred when the skipping test is short and the real work below it is long, because it deals with the odd case first and leaves the main work unindented.

using continue in a while loop before the counter is increased What goes wrong: In a while loop, continue jumps back to the condition test straight away. If the line that increases the counter sits below the continue, it is skipped too — so the counter never moves, the condition never becomes False, and the loop runs for ever. The fix: In a while loop, increase the counter before the continue, or use a for loop, where the loop variable is advanced by Python and cannot be missed.

Leaving data out of a calculation changes the answer, so the rule for leaving it out has to be honest and stated openly. Skipping impossible values such as a negative distance is fair. Skipping readings merely because they spoil the result you were hoping for is not, and in work that affects people — marks, wages, medical readings — it can do real harm. Whenever you drop data, record how many values you dropped and why, as the program above does.

15.5.3 The pass Statement

Python has a rule that catches every beginner sooner or later: a block can never be empty. Write else: with nothing indented under it and the program will not run at all. Python reports an IndentationError, because it reached the end of a block that had not begun.

Most of the time that rule is helpful — an empty block is usually a line you forgot to write. But sometimes an empty block is exactly what you want.

You may be sketching out the shape of a program and want the branches in place before the code that fills them. Or you may have a case that genuinely calls for no action, and you want a reader to see that the case was thought about and deliberately left alone.

The pass statement exists for these moments. It does nothing whatsoever. Its only job is to be a statement, so that a block which has to exist is not empty.

NoteDefinition

pass is a statement that does nothing. It is used as a placeholder wherever Python requires a block but no action is wanted.

A branch left deliberately empty

A robot is working through a list of stored commands. Three letters are understood. Anything else is to be ignored quietly for now, until the team decides what an unknown command should do.

ignoring unknown robot commands Problem: Carry out each known command in a stored list, and take no action at all for a command that is not recognised.

commands = ["F", "L", "X", "B"]

for command in commands:
    if command == "F":
        print("Move forward")
    elif command == "L":
        print("Turn left")
    elif command == "B":
        print("Move back")
    else:
        pass    # unknown commands are ignored for now

print("All commands processed")

Output:

Move forward
Turn left
Move back
All commands processed

The letter X matched none of the three branches, so the else ran — and did nothing, exactly as intended. The loop then carried on to B as usual.

You could delete the whole else branch and the output would be identical. Many programmers would. Keeping it, with a comment, says something the shorter version does not: that unknown commands were considered, and that ignoring them is a decision rather than an oversight.

Q. Is pass the same as continue?

A. No, though in this program they would give the same output. continue skips the rest of the block and starts the next iteration. pass skips nothing — any lines below it in the block still run.

Telling the three apart

Table 15.6 sets out what each of the three keywords does.

Table 15.6 – break, continue and pass | Feature | break | continue | pass | |—|—|—|—| | Effect on the loop | Ends it completely | Ends the current iteration only | No effect | | Lines below it in the block | Not run | Not run | Run as usual | | Iterations still to come | None | All of them | All of them | | Can be used outside a loop | No | No | Yes, in any block |

That last row matters. break and continue only make sense inside a loop, and Python reports a SyntaxError if you write them anywhere else. pass may appear in any block at all — inside an if, inside a case, and, as you will see in Chapter 16, inside a function that has not been written yet.

leaving a block empty instead of writing pass What goes wrong: A header line such as else: with nothing indented beneath it stops the program with an IndentationError. A comment does not help, because a comment is not a statement. The fix: Put pass in the block. If the block is meant to be filled in later, add a comment next to the pass saying what belongs there.

15.6 Choosing Between while and for

You have now used both loops, so the obvious question can be asked properly: given a job that needs repeating, which one should you reach for?

Start by noticing that the two loops overlap. Many tasks can be written either way.

the first five even numbers, written both ways Problem: Print the even numbers from 2 to 10, first with a for loop and then with a while loop.

print("Using a for loop")
for number in range(2, 11, 2):
    print(number)

print("Using a while loop")
number = 2
while number <= 10:
    print(number)
    number = number + 2

Output:

Using a for loop
2
4
6
8
10
Using a while loop
2
4
6
8
10

Identical results. But look at how much each version had to be told. The for loop needed one line: where to start, where to stop, what step. The while loop needed three separate lines — a starting value, a condition, and an increase — spread over three different places in the program, and if any one of them is wrong the loop misbehaves.

That is the practical difference. Where the sequence of values is known in advance, a for loop states it once and cannot lose count.

The real distinction

Table 15.7 sets the two loops side by side.

Table 15.7 – while loop vs for loop | Feature | while loop | for loop | |—|—|—| | What it is given | A condition | A collection of items | | Number of iterations | Not known before the loop starts | Known, because the items already exist | | The loop variable | You create it and update it yourself | Python sets it from the next item | | Risk of an infinite loop | Yes, if the condition never turns False | No, because the items eventually run out | | Natural fit | Repeating until something changes | Repeating once per item |

The row about infinite loops is worth pausing on. A for loop walking a list of five readings will stop after five iterations whatever you do inside it. A while loop offers no such promise, which is why Section 15.4.1 insisted on finding the line that moves the condition towards False.

A rule of thumb

Ask yourself one question before writing the loop: could I say now how many times this must run?

If the answer is yes — six bell beats, every item in a list, every character in a word, every subject in a dictionary — use a for loop.

If the answer is no — keep asking for input until it is valid, keep pumping until the tank is full, keep trying until a sensor changes — use a while loop.

Q. Can every for loop be rewritten as a while loop?

A. Yes, as the program above shows, though it usually takes more lines and more care. The reverse is not true in any natural way: a while loop that waits for something to change has no list of items for a for loop to walk through.

using a while loop to walk through a list What goes wrong: Writing a counter, a condition on the list’s length, and an increase — three chances to make a mistake — where a for loop needed none of them. Forgetting the increase gives an infinite loop; getting the condition slightly wrong runs off the end of the list and raises an IndexError. The fix: If you are walking through the items of a collection, use a for loop. Keep while for the cases where there is no collection to walk through.

15.7 Putting Conditions and Loops to Work

Conditions and loops are rarely used on their own. Almost every useful program is a loop with a condition inside it, and this section works through three examples of that pattern. Read each method first, then the code.

Finding the highest and lowest in a set of readings

A weather project stores the day’s highest temperature for four cities. The task is to report which city was hottest and which was coolest.

You cannot answer this by looking at one city. You have to go through the whole set, remembering the best answer so far and replacing it whenever something better turns up. That “best so far” idea appears again and again in programming.

finding the hottest and coolest city Input: a set of cities, each with a temperature Output: the hottest city and the coolest city, with their temperatures Steps:

1. Set the highest-so-far to a value below every real temperature.
2. Set the lowest-so-far to a value above every real temperature.
3. For each city and its temperature:
   a. If the temperature is above the highest-so-far, make it the new highest and note the city.
   b. If the temperature is below the lowest-so-far, make it the new lowest and note the city.
4. Print the two cities that were noted.

Why it works: after the loop has seen every city, no temperature is left that could beat the values being carried, so the ones being carried must be the extremes.

hottest and coolest city Problem: From a set of four cities and their temperatures, print the hottest and the coolest.

temperatures = {"Jaipur": 41, "Shimla": 24, "Chennai": 36, "Kochi": 33}

hottest = ""
coolest = ""
highest = -100
lowest = 100

for city, temperature in temperatures.items():
    if temperature > highest:
        highest = temperature
        hottest = city
    if temperature < lowest:
        lowest = temperature
        coolest = city

print("Hottest:", hottest, highest, "degrees")
print("Coolest:", coolest, lowest, "degrees")

Output:

Hottest: Jaipur 41 degrees
Coolest: Shimla 24 degrees

The two starting values, −100 and 100, are chosen to sit outside any temperature that could really be recorded, so the very first city is bound to replace both of them. That trick works, but it depends on knowing the range of the data in advance — which is an honest weakness. With a quantity whose range you cannot guess, a safer method is to start from the first item in the set and compare the rest against it.

Notice also that the two if statements are separate, not an if-else. A single city could be both the hottest and the coolest, if there were only one city in the set, and separate tests allow for that.

Checking whether a number is prime

A number is prime if it has no whole-number divisor except 1 and itself. Checking this needs a loop, because every possible divisor has to be tried, and it needs a break, because one divisor found is enough to settle the matter.

checking whether a number is prime Problem: Report whether a given whole number is prime.

number = 91
is_prime = True

if number <= 1:
    is_prime = False
else:
    for divisor in range(2, number):
        if number % divisor == 0:
            is_prime = False
            break

if is_prime:
    print(number, "is a prime number")
else:
    print(number, "is not a prime number")

Output:

91 is not a prime number

The variable is_prime starts as True and is only ever changed to False, so it works as a record of whether anything was found. Such a variable is often called a flag.

91 is a good number to test with, because it looks prime at a glance. The loop tries 2, 3, 4, 5, 6 and finds no divisor, then reaches 7 — and 7 × 13 = 91, so 91 % 7 is 0. The flag is lowered and break ends the loop at once. The remaining 83 divisors are never tried.

This program is correct but not efficient. To test a large number it checks every value up to number - 1, when in fact no divisor can be larger than the square root of the number. Replacing range(2, number) with a range that stops at the square root gives the same answers with far less work.

Q. Why does the program deal with numbers up to 1 separately?

A. Because 1 and 0 are not prime by definition, and the loop would never catch them — range(2, 1) is empty, so the loop body would not run and the flag would be left standing at True.

Building the Fibonacci series

In the Fibonacci series each number is the sum of the two before it, starting from 0 and 1: 0, 1, 1, 2, 3, 5, 8, and so on. The pattern turns up in the branching of plants and in several computing methods.

Here the count of numbers is not known in advance — only the point at which to stop — so this is work for a while loop.

Fibonacci numbers up to a limit Problem: Print every Fibonacci number that does not exceed 100.

limit = 100
a = 0
b = 1

print("Fibonacci numbers up to", limit)

while a <= limit:
    print(a, end=" ")
    next_number = a + b
    a = b
    b = next_number

print()

Output:

Fibonacci numbers up to 100
0 1 1 2 3 5 8 13 21 34 55 89

The three lines at the end of the block do the work. next_number holds the new value while a and b are shifted along, because assigning to a first would destroy the value that next_number still needs.

The loop ends when a becomes 144, which is above the limit. Nothing counted the numbers — twelve of them appeared because that is how many fit under 100.

overwriting a value before it has been used What goes wrong: Writing a = b and then b = a + b gives the wrong series. By the time the second line runs, a already holds the old b, so the sum is of the same number twice. The fix: Work out the new value into a separate variable first, as the program does, and only then move the old values along.

15.8 Summary

  1. Control flow is the order in which the statements of a program are carried out. A plain program runs in a straight line; conditions and loops let you bend that line.
  2. A condition is an expression whose value is only ever True or False. Conditions are built from the comparison operators such as >, <, == and !=.
  3. A block is a group of statements treated as one unit. Python marks a block by indentation, and the accepted style is four spaces. The line that introduces a block always ends with a colon.
  4. An if statement runs its block once when the condition is True and skips it when the condition is False.
  5. An if-else statement gives two paths. Exactly one of the two blocks runs.
  6. An if-elif-else ladder gives many paths. Python tests the conditions from the top and runs the block belonging to the first one that is True, then leaves the statement.
  7. A nested if is one conditional statement inside another. Use it when the inner question only makes sense after the outer one has been answered; use a ladder when the same fact is being split into ranges.
  8. The match statement, available from Python 3.10, compares one value against a list of cases. case _ is the catch-all, matching anything the earlier cases did not.
  9. Shorthand forms exist for small decisions: a single-statement if written on one line, and a conditional expression, which chooses between two values and must always include its else part.
  10. A loop repeats a block of code. One run through the block is called an iteration.
  11. A while loop repeats as long as its condition stays True. It suits work whose length is not known in advance. Something inside the block must move the condition towards False, or the loop never ends.
  12. A for loop runs its block once for each item of an iterable. Lists, tuples, strings, dictionaries and ranges are all iterables, and the loop variable takes a new value each time.
  13. range() produces a run of whole numbers. The starting number is included and the stopping number is not.
  14. Looping over a dictionary gives its keys; looping over .items() gives the key and the value together.
  15. A nested loop is a loop inside a loop. The inner loop runs all the way through for every single iteration of the outer one, so the iterations multiply.
  16. break ends a loop at once. continue abandons only the current iteration. pass does nothing and simply fills a block that must not be left empty.
  17. break and continue work only inside a loop, and in a nested loop they affect only the loop they are written in. pass may be used in any block.
  18. Choose a for loop when you could say in advance how many times the work must run, and a while loop when you could not.
  19. Wherever a program leaves data out of a calculation, the rule for leaving it out should be honest and recorded, because dropping values changes the answer.

15.9 Key Terms

  1. Block – a group of statements that belong together and are carried out as one unit.
  2. break statement – a statement that ends a loop at once and moves the program on to the line after the loop.
  3. Condition – an expression whose value is either True or False.
  4. Conditional expression – a one-line form that gives one of two values, chosen by a condition; also called the ternary operator.
  5. Conditional statement – a statement that tests a condition and carries out a block of code depending on the answer.
  6. continue statement – a statement that skips the rest of the current iteration and starts the next one, leaving the loop running.
  7. Control flow – the order in which the statements of a program are carried out.
  8. elif statement – short for “else if”; an extra condition hung under an if, tested only when the conditions above it are False.
  9. for loop – a loop that runs its block once for every item in a collection.
  10. if statement – a statement that runs a block of code once if its condition is True, and skips it if the condition is False.
  11. if-else statement – a statement that runs one block when its condition is True and a different block when it is False.
  12. if-elif-else ladder – a chain of conditions tested in order from the top, of which only the first True one has its block run.
  13. Indentation – the blank space at the start of a line, which in Python marks which lines belong to a block.
  14. Infinite loop – a loop whose stopping condition never becomes False, so it runs until the program is forced to stop.
  15. Iterable – an object able to hand over its items one at a time, such as a list, tuple, string, dictionary or range.
  16. Iteration – one single run through the block of a loop.
  17. Loop – a statement that repeats a block of code.
  18. Loop control statements – the statements break, continue and pass, which change the ordinary flow of a loop from within its own block.
  19. Loop variable – the variable in a for loop that Python fills with the next item at the start of each iteration.
  20. match statement – a statement that compares one value against a list of cases and runs the block belonging to the first case that matches.
  21. Nested loop – a loop written inside the block of another loop, so the inner loop runs completely for every iteration of the outer one.
  22. Nesting – writing one conditional statement inside the block of another.
  23. pass statement – a statement that does nothing, used as a placeholder where Python requires a block but no action is wanted.
  24. while loop – a loop that repeats its block for as long as its condition stays True.

15.10 Objective Questions

  1. Which keyword ends a loop immediately and moves the program on to the line after it?

    1. pass
    2. continue
    3. break
    4. elif
  2. In Python, a block is marked by ______ rather than by brackets.

  3. State whether the following is true or false: an else branch may carry a condition of its own.

  4. Which numbers does range(2, 9, 3) produce?

    1. 2, 4, 6, 8
    2. 2, 5, 8
    3. 3, 6, 9
    4. 2, 5, 8, 11
  5. Write the output of the following program.

for n in range(3):
    if n == 1:
        continue
    print(n)
  1. The number given as the stopping value of range() is ______ included in the numbers it produces.

  2. Match each keyword in the left column with its purpose in the right column.

    Column A Column B
    a. while i. walks through the items of a collection, one at a time
    b. for ii. fills a block that must exist but needs no action
    c. pass iii. catches every value that the earlier cases did not match
    d. case _ iv. repeats a block for as long as a condition stays true

15.11 Concept Questions

  1. What is a condition? Give one example of a condition written in Python.

  2. Explain the difference between break and continue, and give one situation in which each is the right choice.

  3. Explain what case _ does in a match statement, and say which part of an if-elif-else ladder plays the same part.

  4. Why can a while loop run for ever while a for loop over a list cannot? Explain with reference to how each loop decides to carry on.

  5. When should a nested if be used in place of an if-elif-else ladder? Support your answer with one example of your own.

  6. In a while loop, the line that increases the counter has been written below a continue statement. Explain what will happen when the program is run, and how it should be corrected.

  7. A list holds five distance readings taken by a robot. Write a program that prints each reading, together with the message too close for any reading below 25 cm.

15.12 Case-Based Questions

  1. A school’s overhead tank has a level sensor. The maintenance rule is: below 30 per cent, switch the pump on; from 30 up to 80 per cent, leave the pump as it is; above 80 per cent, switch the pump off. Four readings taken during one day are shown below.

    Time Level (per cent of full)
    8 a.m. 18
    11 a.m. 45
    2 p.m. 72
    5 p.m. 95
    1. Which conditional form suits this rule best, and why is a single if-else not enough?
    2. Write a program that stores one level in a variable and prints the correct instruction for it.
    3. A level of exactly 30 is recorded. State which instruction your program prints for it, and explain how you would change the program if the school wanted the opposite result.
  2. A robot scans a corridor and stores its distance readings in a list. A student writes the following program to find the first point at which something blocks the way.

readings = [60, 48, 15, 33]

for reading in readings:
    if reading < 20:
        print("stop")
        break
    print(reading)
  1. Write the output of the program as it stands.
  2. The team now wants the robot to ignore any negative reading, which means the sensor received no echo, and to carry on checking the rest of the list. Rewrite the loop to do this.
  3. Explain why part (a) needs break while part (b) needs continue.