Chapter 6 Β· Data Structures

Lists & Comprehensions

Every list method you need, lists as stacks and queues, the one-line power of comprehensions, and the new 3.15 unpacking syntax.

You will learn

  • The list methods, and which ones change the list in place.
  • Using lists as stacks, and collections.deque as a queue.
  • List comprehensions, with filters and nested loops.
  • * unpacking in list displays and, new in 3.15, inside comprehensions.
  • The del statement, and the "list of lists" copying trap.

List methods

MethodWhat it doesReturns
lst.append(x)add x to the endNone
lst.extend(iterable)add every item of iterable to the endNone
lst.insert(i, x)insert x before index iNone
lst.remove(x)remove the first item equal to x (ValueError if absent)None
lst.pop([i])remove and return the item at i (default: the last)the item
lst.clear()remove everythingNone
lst.index(x[, start[, end]])position of the first x (ValueError if absent)an int
lst.count(x)how many times x appearsan int
lst.sort(*, key=None, reverse=False)sort in placeNone
lst.reverse()reverse in placeNone
lst.copy()a shallow copy (same as lst[:])a new list

⚠️ Pitfall: in-place methods return None. Methods that change the list (sort, reverse, append, …) return None, not the list. This is a deliberate design rule in Python: "a method that mutates returns nothing".

❌ Wrong

βœ… Right

append vs extend

Sorting with key and reverse

key is a function that is applied to each item to get the value to sort by. The sort is stable: items with equal keys keep their original order.

Not all values can be compared with each other. [None, 'hello', 10] cannot be sorted, because an integer can't be compared with a string:

β˜• Java: key= replaces writing a Comparator. Python asks "what do I sort by?" instead of "how do I compare two items?". 🟨 JS: unlike Array.prototype.sort(), Python sorts numbers numerically by default, so [10, 9, 1] becomes [1, 9, 10].

Lists as stacks

A stack is "last in, first out" (LIFO), like a pile of plates. append() pushes and pop() pops. Both are fast.

Lists as queues? Use deque

A queue is "first in, first out" (FIFO), like a line at a shop. A list can do it, but pop(0) and insert(0, x) are slow: every other item has to shift over by one. Use collections.deque (a "double-ended queue"), which is fast at both ends.

NeedUse
Stack (LIFO)list with append / pop
Queue (FIFO)deque with append / popleft
Keep only the last N itemsdeque(maxlen=N)
Always get the smallest item firstheapq (Chapter 14)

List comprehensions

A list comprehension builds a new list from an iterable in one readable expression. The shape is [expression for item in iterable if condition].

Read it like English: "give me x squared, for each x in range(10)".

Loop version

Comprehension

Unlike the loop, the comprehension doesn't leave a leftover x variable behind afterwards.

πŸ’‘ Where does the if go? A filter goes at the end: [x for x in xs if x > 0], which drops items. A conditional expression goes at the start: [x if x > 0 else 0 for x in xs], which keeps every item and picks a value for each.

Several for clauses

Several fors in one comprehension work like nested loops, written in the same order you would nest them:

β˜• Java / 🟨 JS: a comprehension replaces stream().filter().map().collect() or xs.filter(...).map(...). map() and filter() also exist in Python, but they return lazy iterators, and comprehensions are usually considered clearer.

Nested list comprehensions

The first expression of a comprehension can itself be a comprehension. A classic use is transposing a matrix (swapping its rows and columns):

⚠️ Readability first: if a comprehension needs more than two fors or a long condition, write a normal loop. Clever one-liners are hard to debug.

Unpacking in lists and comprehensions

Inside a list display, *expr spreads the items of expr into the new list, like JS's spread ...x.

πŸ“ New in 3.15 (PEP 798): * also works inside a comprehension, which makes flattening a list of lists a one-liner.

πŸ“ Older syntax you'll still see: before 3.15 you flattened with a double for, [item for sub in x for item in sub], or with itertools.chain.from_iterable(x). Both still work.

Star unpacking also works on the left side of an assignment:

The del statement

del removes items by index or slice (unlike pop, it returns nothing). It can also delete a whole variable name.

To remove…Use
by value (the first match)lst.remove(value)
by index, and you want the item backlst.pop(i)
by index or slice, without the item backdel lst[i] / del lst[i:j]
everything matching a conditiona comprehension: [x for x in lst if keep(x)]

The list-of-lists trap

[x] * 3 repeats the same object three times. With a list inside, all three "rows" are one and the same list.

❌ Three names for one row

βœ… A new row each time

The same thing happens with copies: lst.copy() and lst[:] are shallow. They copy the outer list, but the inner lists are still shared. Use copy.deepcopy() for a full copy of nested data.

More examples

1. Cleaning a column of user input

2. An undo stack

3. The last 3 messages with deque(maxlen=…)

4. Top-N expenses

5. Flattening receipts from several days (3.15)

Recap

  • Mutating methods (sort, append, reverse…) return None. sorted() returns a new list. Sort with key= and reverse=.
  • Use a list as a stack (append/pop), and use deque for queues (popleft).
  • [expr for x in xs if cond]: a filter goes at the end, a conditional value goes at the start, and several fors nest left to right.
  • [0, *xs] spreads; [*sub for sub in xss] flattens (3.15); first, *rest = xs unpacks.
  • del lst[i:j] removes by position. [[0]*3]*3 shares rows; use a comprehension instead.

Exercises

Exercise 1 🟒 easy predict the output

After a = [1, 2, 3]; a.insert(1, 9); a.pop(); a.append(4), what is a?

Show solution

[1, 9, 2, 4]. The insert gives [1, 9, 2, 3], pop() removes the last item (3), and append adds 4.

Exercise 2 🟒 easy build it

Write a comprehension that gives the lengths of ["spam", "eggs", "ham"].

Show solution

The expression part (len(w)) is applied to every item.

Exercise 3 🟒 easy fix the bug

This prints None. Fix it so it prints the sorted list.

Show solution

list.sort() sorts in place and returns None. sorted() returns a new list.

Exercise 4 🟒 easy predict the output

What are a and b after a = [1]; a.append([2, 3]) and b = [1]; b.extend("23")?

Show solution

a == [1, [2, 3]] and b == [1, '2', '3']. append adds one object. extend loops over its argument, and a string is iterated character by character.

Exercise 5 🟑 medium refactor it

Turn this loop into a single comprehension.

Show solution

The if filter goes at the end, and the transformation at the start.

Exercise 6 🟑 medium predict the output

What does [(a, b) for a in "xy" for b in range(2)] produce?

Show solution

[('x', 0), ('x', 1), ('y', 0), ('y', 1)]. The first for is the outer loop.

Exercise 7 🟑 medium build it

Simulate a printer queue with deque: jobs "a.pdf", "b.pdf" and "c.pdf" arrive. Print one job, then "d.pdf" arrives, then print everything that remains in order.

Show solution

First in, first out: append at the right, popleft from the left. Both are O(1), meaning constant time no matter how long the queue is.

Exercise 8 🟑 medium build it · playground

Create playground/clean.py. Given lines = ["food,12.5", "bad line", "rent,900", "fun,abc", "books,25"], use comprehensions to build a list of (category, amount) tuples, keeping only lines that have exactly one comma and a numeric amount.

Show solution

Two small comprehensions, one that splits and one that validates and converts, read better than one giant one. Chapter 10 shows the try/except way to validate numbers.

Exercise 9 πŸ”΄ hard explain why

Why do these give different results? [[]] * 2 versus [[] for _ in range(2)], after appending 1 to the first inner list.

Show solution

* copies references, so a holds one empty list twice. The comprehension runs [] on every pass and creates a new list each time.

Exercise 10 πŸ”΄ hard predict the output

What is m?

Show solution

The first line builds [[0, 1, 2], [3, 4, 5]]. Only the second row has a sum greater than 3 (12), so the flattening comprehension gives [3, 4, 5]. (This needs Python 3.15 for the *row part.)

Exercise 11 πŸ”΄ hard mini-project Β· playground

Create playground/ledger.py: a tiny in-memory ledger with add(cat, amount), undo(), top(n) (the n largest as tuples) and by_category(cat) (the amounts for one category). Use a list for the data and a list as an undo stack.

Show solution

It combines a stack (append/pop), del by index, sorted with a key, slicing and a filtering comprehension.

Official docs