◀ Course contents Part 2 · Module 2-04

Lists

One name for many values, in an order you control

You have already met lists without being introduced: split() hands one back every time. This module makes them yours to build and change. A list is the first container in the track, and the first thing you have met that can be modified in place — which is most of its power and both of its famous bugs.

Ready?

1

A List Is an Ordered, Changeable Sequence

Square brackets, values separated by commas. The items keep the order you put them in, and they do not have to be the same type — though in practice a list whose items mean different things is usually a sign that something else was wanted.

tags = ["loops", "lists", "functions"]

tags[0]      # "loops"      — counting from zero
tags[-1]     # "functions"  — from the end
tags[1:]     # ["lists", "functions"]
len(tags)    # 3
"lists" in tags   # True

All of that is exactly what strings did in module 1-04, and for the same reason: both are sequences, so indexing, slicing, len() and in work the same way on either. Learning it once was the point.

Slicing a list hands back a new list. Indexing one item hands back the item itself. So tags[0] is a string and tags[0:1] is a list of one string — a distinction that looks pedantic until it is the reason something crashes.

An index that does not exist raises

tags[9] on a three-item list is an IndexError, not None and not an empty string. A slice is more forgiving: tags[5:9] quietly hands back []. That asymmetry is worth knowing before it surprises you at the end of a loop.

Quick check

What is len(["a", "b", "c"][1:])?

2

Adding, Removing, Replacing

Unlike a string, a list can be changed in place. That is what mutable means, and it is why a list is what a loop builds into.

append(x)

One item on the end. The workhorse.

extend(other)

Every item of another list on the end. append would have added the list itself as one item.

insert(i, x)

At a position. Everything after it shuffles up.

remove(x)

The first item equal to x. ValueError if there is none.

pop()

Takes the last item off and hands it back. pop(0) takes the first.

queue = []
queue.append("order-1")
queue.append("order-2")

next_up = queue.pop(0)    # "order-1", and the queue is now one shorter
queue[0] = "order-9"      # replace in place

The important half: these methods change the list and hand back None. tags = tags.append("x") throws your list away and leaves tags holding nothing at all. Call the method; do not assign its result.

Quick check

What does tags = tags.append("new") leave in tags?

3

Ordering and Summarising

Two ways to sort, and the difference matters:

scores.sort()             # changes scores, returns None
ranked = sorted(scores)   # leaves scores alone, returns a new list

sorted(scores, reverse=True)   # biggest first

sort() is the in-place one and shares the None trap with append. sorted() is the function that hands back a new list, and it works on anything you can loop over, not just lists.

Sorting compares items as they are, which means a list of numbers that arrived as text sorts alphabetically: ["100", "20", "9"] sorts to ["100", "20", "9"], because "1" comes before "2". Convert first.

Four built-ins summarise a list without a loop, and reading them is faster than reading the loop they replace:

sum(scores)   min(scores)   max(scores)   len(scores)

The accumulator loop from the last module is still worth knowing — you need it the moment the rule is anything other than these four — but when one of them fits, use it.

Quick check

You need the original list untouched and a sorted copy. Which?

4

Two Bugs That Only Mutable Things Can Have

Aliasing. b = a does not copy a list. It points a second name at the same one, and this is the moment the "a variable is a label, not a box" idea from module 1-02 starts costing money:

a = [1, 2, 3]
b = a
b.append(4)
print(a)      # [1, 2, 3, 4]  — a changed too

When you want an independent copy, ask for one: b = a.copy(), or the older b = a[:]. Both build a new list holding the same items.

Mutating while looping. Removing items from the list you are currently walking makes the loop skip things. The loop tracks a position; take an item out and everything after it slides down one, into a position the loop has already passed:

for name in names:
    if name.startswith("test-"):
        names.remove(name)    # silently skips the next one

The fix is not a cleverer loop. Build a new list of the ones you are keeping, and rebind the name at the end. That is also the shape comprehensions replace in module 3-03.

Both bugs are quiet

Neither of these raises. Aliasing produces a list that is correct somewhere else in the program; mutating while looping produces a filter that misses roughly half of what it should have caught, in a pattern that depends on where the matches happened to sit.

If a function takes a list and changes it, say so in its name — or copy it first and hand a new one back.

Quick check

Why does removing items inside a for loop skip some of them?

0 of 9 completed

Real Python runs right here in your browser — nothing to install, nothing sent to a server. The interpreter downloads once the first time you press Run, then stays cached.

01

To do

Square brackets, values separated by commas. Indexing and slicing work exactly as they did on strings, because both are sequences.

tags[0]     # the first item
tags[-1] # the last one
tags[1:3] # a NEW list of items 1 and 2
len(tags) # how many

Note the difference between the second and third lines: tags[0] hands back the item, tags[0:1] hands back a list containing it.

Your task: print four lines — the first tag, the last tag, a slice of the middle two, and the count:

loops
errors
['lists', 'functions']
4
your_code.py
Python
Hint

tags[0] and tags[-1] for the ends. The middle two are tags[1:3] — start at 1, stop before 3. len(tags) counts them.

Output

      
    02

    To do

    append() puts one item on the end. Starting from an empty list and appending inside a loop is the accumulator pattern again, with a list instead of a number.

    cleaned = []
    for tag in tags:
        cleaned.append(tag.strip())

    Your task: the tags arrive with inconsistent spacing. Build cleaned, stripped and title-cased, then print the list and how many there are:

    ['Loops', 'Lists', 'Functions']
    3
    your_code.py
    Python
    Hint

    Split raw on the comma first, then loop over the pieces and append tag.strip().title() to cleaned.

    Output
    
          
      03

      To do

      Four methods cover most of it. Three change the list and hand back None; pop() is the exception — it removes an item and gives it to you.

      queue.append("x")     # on the end
      queue.insert(0, "x") # at a position
      queue.remove("x") # the first one equal to x
      item = queue.pop(0) # take the first off and keep it

      Your task: run the queue through four changes, in this order: put order-3 on the end, put order-0 at the front, take the front one off into next_up, and remove order-2. Then print:

      order-0
      ['order-1', 'order-3']
      2
      your_code.py
      Python
      Hint

      append, then insert(0, ...), then next_up = queue.pop(0), then queue.remove("order-2"). Only pop hands something back — the others are called for their effect.

      Output
      
            
        04

        To do

        The single most common list mistake there is. append() changes the list and returns None, so assigning its result throws the list away and leaves the name holding nothing.

        tags = tags.append("x")   # tags is now None

        The same is true of sort(), insert(), remove(), extend() and reverse(). They are called for their effect. Only pop() hands something useful back.

        The error usually arrives much later, somewhere else, as TypeError: 'NoneType' object is not iterable — which is why recognising the cause is worth more than reading that message.

        Your task: fix the line so it prints the list:

        ['loops', 'lists', 'functions']
        your_code.py
        Python
        Hint

        Call the method and leave the name alone: tags.append("functions"). The list is changed in place, so there is nothing to assign.

        Output
        
              
          05

          To do

          scores.sort() reorders the list in place and returns None. sorted(scores) leaves the original alone and hands back a new list.

          When you need both the original order and a ranking — which is most report code — sorted() is the one. reverse=True puts the biggest first.

          Your task: build ranked, highest first, without disturbing scores. Print both:

          [88, 54, 92, 71]
          [92, 88, 71, 54]
          your_code.py
          Python
          Hint

          sorted(scores, reverse=True) builds a new list. Using scores.sort() would reorder the original and hand back None.

          Output
          
                
            06

            To do

            Sorting compares the items as they are. A list of numbers that arrived as text is a list of text, so it sorts alphabetically: every string starting with 1 comes before every string starting with 2, whatever the numbers mean.

            sorted(["100", "20", "9"])   # ['100', '20', '9']

            No error, no warning — a ranking that is confidently in the wrong order. Convert first, then sort.

            Your task: print the wrong sort, then build numbers by converting each piece, and print the right one:

            ['100', '20', '9']
            [9, 20, 100]
            your_code.py
            Python
            Hint

            Loop over raw and append int(piece) to numbers. Then sorted(numbers) compares numbers rather than characters.

            Output
            
                  
              07

              To do

              sum(), min(), max() and len() each do in one word what an accumulator loop does in four lines — and they read faster, which is the real argument.

              The loop is still the tool the moment the rule is anything other than these. But when one fits, reaching past it is just more code to get wrong.

              Your task: print a five-line summary of the scores, with the average to two decimal places:

              Count: 4
              Total: 305
              Lowest: 54
              Highest: 92
              Average: 76.25
              your_code.py
              Python
              Hint

              len, sum, min and max, then sum(scores) / len(scores) for the average with a {:.2f} spec.

              Output
              
                    
                08

                To do

                backup = original does not copy anything. It points a second name at the same list — the "a variable is a label, not a box" idea from module 1-02, finally with teeth.

                a = [1, 2]
                b = a
                b.append(3)
                print(a) # [1, 2, 3]

                Strings never behaved like this because a string cannot be changed at all. A list can, and every name pointing at it sees the change.

                When you want an independent list, ask for one: a.copy(), or the older a[:].

                Your task: make backup a genuine copy, taken before the change, so the output is:

                ['a', 'b', 'c']
                ['a', 'b']
                your_code.py
                Python
                Hint

                backup = original.copy() builds a new list holding the same items, so appending to one leaves the other alone.

                Output
                
                      
                  09

                  To do

                  Removing items from the list you are currently walking makes the loop skip things. The loop advances by position; take one out and everything after it slides down into a position already visited.

                  The code below is meant to drop every test- account and leaves one behind. It raises nothing, and how many it misses depends on where they happened to sit.

                  Your task: rewrite it to build a list of the ones you are keeping, and point names at that. Do not remove from a list while looping over it.

                  ['kenji', 'ada', 'grace']
                  your_code.py
                  Python
                  Hint

                  Start a new empty list, loop over the original, and append the names you want to keep. Assign that list to names at the end.

                  Output
                  
                        

                    The order book

                    To do

                    A supplier's order file has landed. Read it once, drop what should not be counted, and report on what is left.

                    Each real line is sku,name,quantity,unit price. Blank lines and lines starting with # are not orders. Neither is a line whose quantity is 0 — that item is out of stock and must not appear anywhere in the report.

                    Build these:

                    • names — the names of the in-stock items, in file order
                    • line_totals — quantity times unit price for each, in the same order
                    • order_value — the whole order
                    • biggest — the name of the item with the largest line total
                    • ranked — the line totals, largest first, leaving line_totals in file order

                    Then print exactly five lines:

                    Items: 4
                    Order value: 22680
                    Biggest line: whisk (11880)
                    Ranked: [11880, 4800, 3750, 2250]
                    Cheapest line: 2250
                    your_code.py
                    Python
                    Hint

                    Loop over raw.splitlines(), strip each line, and continue past the blanks and the # lines. Split what is left on the comma into four pieces; the quantity and price need int(). Skip a quantity of 0 with another continue. Append the name and the line total to their lists together, so the positions stay in step. For the biggest, line_totals.index(max(line_totals)) gives you the position, and names at that position gives you the name.

                    Output
                    
                          

                      Notification