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.dequeas a queue. - List comprehensions, with filters and nested loops.
*unpacking in list displays and, new in 3.15, inside comprehensions.- The
delstatement, and the "list of lists" copying trap.
List methods
| Method | What it does | Returns |
|---|---|---|
lst.append(x) | add x to the end | None |
lst.extend(iterable) | add every item of iterable to the end | None |
lst.insert(i, x) | insert x before index i | None |
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 everything | None |
lst.index(x[, start[, end]]) | position of the first x (ValueError if absent) | an int |
lst.count(x) | how many times x appears | an int |
lst.sort(*, key=None, reverse=False) | sort in place | None |
lst.reverse() | reverse in place | None |
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.
| Need | Use |
|---|---|
| Stack (LIFO) | list with append / pop |
| Queue (FIFO) | deque with append / popleft |
| Keep only the last N items | deque(maxlen=N) |
| Always get the smallest item first | heapq (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 back | lst.pop(i) |
| by index or slice, without the item back | del lst[i] / del lst[i:j] |
| everything matching a condition | a 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β¦) returnNone.sorted()returns a new list. Sort withkey=andreverse=. - Use a list as a stack (
append/pop), and usedequefor queues (popleft). [expr for x in xs if cond]: a filter goes at the end, a conditional value goes at the start, and severalfors nest left to right.[0, *xs]spreads;[*sub for sub in xss]flattens (3.15);first, *rest = xsunpacks.del lst[i:j]removes by position.[[0]*3]*3shares 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
- 5.1 More on Lists (stacks, queues, comprehensions, unpacking)
- 5.2 The del statement
- Sorting Techniques (HOWTO)
- collections.deque