A program runs from top to bottom unless something tells it otherwise. Control flow statements are that something: they choose which block runs (if, match), repeat a block (while, for), and leave or skip part of a loop (break, continue). Almost every program you write from now on uses them.
This unit also covers the loop helpers that make Python loops short and safe (range, enumerate, zip, dict.items()), structural pattern matching with match, and two classic traps: leaving nested loops, and changing a list while you loop over it.
You will be able to:
- Write
if/elif/elsechains that test truthiness directly instead of comparing withTrue - Use
match/casewith literal, capture, sequence, mapping and class patterns, guards and_ - Choose between
whileandfor, and loop withrange,enumerate,zipanddict.items() - Control loops with
break,continue,passand the loopelseclause - Leave nested loops cleanly, and avoid changing a list while you iterate over it
- Write an input loop that keeps asking until the answer is valid
Blocks and indentation
A compound statement such as if, for or while has a header line ending in a colon, followed by an indented block of statements. The indentation is the only thing that marks where the block ends, so it must be consistent: PEP 8 asks for four spaces per level, and mixing tabs and spaces raises TabError.
Indentation defines blocks, not scopes. A name assigned inside an if or a loop is an ordinary variable of the surrounding code and still exists afterwards. if, for, while and match never create a new scope; functions do (Functions explains the lookup rules).
if, elif and else
Python checks each condition in turn, runs the block of the first one that is true and skips the rest. Both elif and else are optional, and there can be any number of elif branches.
The diagram traces the example below with x = 3: only the first true branch runs, and else catches everything else.
Truthiness in conditions
A condition doesn't have to be a bool. Python uses the value's truthiness (Values, names and types), so test containers and strings directly:
Write if basket: rather than if len(basket) > 0:, and never if flag == True:. When 0 or "" is a valid value, as discount is here, test is None / is not None instead of truthiness. For choosing between two values, use the conditional expression a if condition else b from Operators and expressions.
Pitfall.
if colour == "red" or "blue":is always true. It means(colour == "red") or "blue", and the nonempty string"blue"is truthy. Writeif colour in ("red", "blue"):.
Pattern matching with match
Since Python 3.10, match compares one value, the subject, against a series of case patterns from top to bottom. The block of the first pattern that matches runs, and then the statement ends: there is no fall through to the next case, and if nothing matches, nothing runs. Patterns can do much more than compare values; they can also take a value apart and bind its pieces to names.
| Pattern | Example | Matches |
|---|---|---|
| literal | case 404: | an equal value (None, True, False by identity) |
| alternatives | case 200 | 201: | any of the alternatives |
| wildcard | case _: | anything, and binds nothing |
| capture | case code: | anything, and binds it to code |
| value | case HTTPStatus.OK: | a value equal to that dotted constant |
| sequence | case [x, *rest]: | a list, tuple or other sequence of that shape (not a string) |
| mapping | case {"type": kind}: | a dict or other mapping with those keys (extra keys are fine) |
| class | case int(n):, case Point(x=0): | an instance of the class with matching attributes |
| guard | case [x, y] if x == y: | the pattern matches and the condition is true |
as | case [_, _] as pair: | the pattern, binding the whole value to pair |
The examples put each match inside a small function so that you can try several values with a loop.
Literals, constants, captures and _
A bare name in a pattern is a capture pattern: it matches anything and assigns the value to that name. It never compares against an existing variable. To compare against a named constant, use a dotted name such as HTTPStatus.NOT_FOUND, which Python treats as a value.
Pitfall. With
NOT_FOUND = 404andcase NOT_FOUND:, the case doesn't compare with 404; it captures every value into a new variable calledNOT_FOUND. When later cases follow it, Python refuses to compile the statement:SyntaxError: name capture 'NOT_FOUND' makes remaining patterns unreachable.
Sequence patterns
A sequence pattern matches a list or tuple of a given shape. *name collects any remaining items into a list. A common use is matching a command split into words:
Mapping patterns
A mapping pattern checks that a dict has certain keys, optionally with certain values, and captures the values you name. Extra keys are ignored, which suits JSON like data.
Class patterns and guards
A class pattern such as int() or str(text) checks the type with isinstance, and for built in types like these str(text) binds the value itself. A guard, if condition after the pattern, adds a test that must also pass.
Order matters: bool is a subclass of int, so case int(): would also match True. Put the more specific case first.
Class patterns also work with your own classes, matching attributes by name, as in case Point(x=0, y=y):. Classes and objects shows how to define such a class. Its section Matching objects with match has a runnable example, and explains what a positional pattern such as case Point(0, y): needs.
Tip.
matchis worth it when you are checking the shape of data: commands, messages, parsed JSON. For a plain "which of these values?" choice,if/elifor a dict lookup is often as clear.
while loops
A while loop repeats its block as long as its condition is true. The condition is tested before each pass, so the body may run zero times.
Something in the body must eventually make the condition false. If a loop runs forever by mistake, press Ctrl+C to stop it.
while True with break
Python has no do ... while loop. When the exit test belongs in the middle or at the end of the body, write while True: and leave with break. The body always runs at least once:
The random generator is seeded so that the output is repeatable; A tour of the standard library covers random.
for loops
A for loop takes the items of an iterable one at a time: a list, tuple, string, dict, set, file, range and many more. You don't manage an index or a stop condition; the loop ends when the items run out. Iterators and generators shows how this works underneath.
range(start, stop, step)
stop is never included, start defaults to 0, and step may be negative. A range doesn't build a list; it calculates each number when asked, so range(10**12) costs no memory. The list() calls above are only there to display the numbers.
enumerate, zip and dicts
To get positions as well as items, use enumerate rather than range(len(...)). To walk two sequences side by side, use zip. To loop over a dict's keys and values together, use .items(); looping over the dict itself gives only the keys, in insertion order.
zip stops silently at the end of the shortest input, which can hide a bug where two lists should have the same length. Since Python 3.10, zip(..., strict=True) raises an error instead:
This prints the first two pairs, then stops with ValueError: zip() argument 2 is shorter than argument 1. Use .values() when you need only the values, sorted(prices) for keys in sorted order, and reversed(colours) to walk a sequence backwards.
break, continue, else and pass
breakleaves the innermost loop immediately.continueskips the rest of the current pass and goes on to the next item.- A loop's
elseblock runs when the loop ends withoutbreak: the iterable ran out, or thewhilecondition became false. Read it as "no break". passdoes nothing. Use it where the syntax needs a statement but you have nothing to do yet. It does not skip anything; that iscontinue's job.
A while loop works the same way, except that it retests its condition instead of fetching the next item. Its else runs when the condition becomes false, and is skipped when break ends the loop:
In the block below, the first loop uses continue to skip missing readings. The second shows where else shines: a search that uses break when it finds something and lets else handle "not found" without a flag variable.
Leaving nested loops
break leaves only the innermost loop, and Python has no labelled break. There are two common ways out of several loops at once: a flag that the outer loop checks, or putting the loops in a function and using return.
The function version is usually clearer: return leaves every loop at once, and the function gets a name that says what it does. A third option is itertools.product, which produces every pair for a single loop, so that one break is enough. Iterators and generators introduces it.
Changing a list while you loop over it
Removing items from a list while a for loop walks over it makes the loop skip items:
The loop keeps an internal position. Removing 2 shifts every later item one place left, so 4 moves into the slot that was already visited and is never checked. The fixes build a new list, or loop over a copy while changing the original. The new list is made by a list comprehension, covered in Collections and comprehensions; numbers[:] is a copy made by slicing. Dicts and sets are stricter than lists: adding or removing entries during a loop raises RuntimeError (dictionary changed size during iteration for a dict). Loop over list(the_dict) if you need to delete keys. Changing the values of existing keys is fine.
An input validation loop
input() always returns a string, and int() raises ValueError when the string isn't a whole number. A while True loop can keep asking until the answer is valid. The try/except statement catches the error; Errors and exceptions covers it in detail.
The answers abc, 42 and 7 were fed in automatically, so they don't appear in the output; when you type them yourself, each shows after its prompt.
Check yourself
- After
if ready: status = "go"runs withreadytrue, doesstatusexist outside the block? - Why is
if answer == "y" or "Y":always true, and how do you fix it? - In a
match, what is the difference betweencase limit:andcase Limits.MAX:? - In
classify()above, what wouldTruematch ifcase int() | float():came beforecase bool():? - When does the
elseblock of aforloop run? - What does
strict=Trueadd tozip()? - What does
list(range(10, 0, -4))give? - Why does removing items from a list inside
for item in that_listskip some of them?
Suggested answers
- Yes. Indentation creates blocks, not scopes, so
statusis an ordinary variable of the surrounding code. - It means
(answer == "y") or "Y", and"Y"is truthy. Writeif answer in ("y", "Y"):, orif answer.lower() == "y":. case limit:is a capture pattern: it matches anything and binds it tolimit.case Limits.MAX:is a value pattern: it matches only a value equal toLimits.MAX.case int() | float():, becauseboolis a subclass ofint, and the first matching case wins.- When the loop finishes without
break, because the items ran out. It also runs if there were no items at all. - It raises
ValueErrorwhen the inputs have different lengths, instead of silently stopping at the shortest. [10, 6, 2]. The stop value 0 is never reached or included.- Removing an item shifts the later items left, but the loop's position still moves forward, so the item that moved into the current slot is never visited.
Exercises
These exercises are optional practice. Pick the ones that help you.
- Sevens but not fives. Write
sevens(start, stop)that returns every number fromstarttostopinclusive that is divisible by 7 but not by 5. Print the result for 1 to 100 on one line, separated by commas. Expected:7,14,21,28,42,49,56,63,77,84,91,98. - Factorial. Read a whole number
nwithinput()and printn!(1 × 2 × … × n), computed with a loop.0!is 1; for a negativen, raiseValueError. Input8→40320. - List and tuple from a line. Read one line of comma separated whole numbers, possibly with spaces around them, and print them as a list of ints and as a tuple. Input
3, 14,15 ,92,65→[3, 14, 15, 92, 65]and(3, 14, 15, 92, 65). (line.split(",")cuts a string at each comma, andint()ignores surrounding spaces; Strings and text formatting covers both.) - Shout until blank. Read lines until an empty line, and print each one in capitals (
line.upper()). Inputhello world,practice makes progress, then an empty line →HELLO WORLDandPRACTICE MAKES PROGRESS. Anything after the empty line is not read. - FizzBuzz with
match. Writefizzbuzz(n)that returns"FizzBuzz"ifnis divisible by both 3 and 5,"Fizz"if only by 3,"Buzz"if only by 5, and otherwise the number as a string. Usematch (n % 3, n % 5):. Print the results for 1 to 15 on one line:1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz.
Solutions
1. Sevens but not fives. print(*items, sep=",") passes each item as a separate argument and puts commas between them.
2. Factorial. The loop starts at 2 because multiplying by 1 changes nothing; for n of 0 or 1 it doesn't run at all and the result stays 1.
3. List and tuple from a line.
4. Shout until blank. An empty string is falsy, so if not line detects the blank line. If the input might end without one, catch EOFError around input() as well.
5. FizzBuzz with match. Matching on a tuple of both remainders puts the whole decision in one place; _ means "any value" in that position.