Python Lists, Tuples, and Comprehensions
- Description:
list(mutable) vstuple(immutable), slicing semantics, sorting withkey=, copying, and list/dict/set comprehensions - My Notion Note ID: K2A-D1-5
- Created: 2022-05-15
- Updated: 2026-05-11
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1.
list(Mutable Sequence) - 2.
tuple(Immutable Sequence) - 3. Iteration Helpers
- 4. Sorting
- 5. Copying
- 6. Comprehensions
1. list (Mutable Sequence)
- Dynamic array, equivalent of C++
std::vector - Mixed types allowed:
[1, "two", 3.0]is fine
1.1 Construction
[]
[1, 2, 3]
list("abc") # ['a', 'b', 'c']
list(range(5)) # [0, 1, 2, 3, 4]
[0] * 5 # [0, 0, 0, 0, 0]
[None] * n # preallocated list
1.2 Methods
xs = [1, 2, 3]
xs.append(4) # [1, 2, 3, 4] , O(1) amortized
xs.extend([5, 6]) # [1, 2, 3, 4, 5, 6]
xs.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6] , O(n)
xs.pop() # returns 6, pop from end, O(1)
xs.pop(0) # returns 0, pop from front, O(n)
xs.remove(3) # remove first occurrence
xs.reverse()
xs.sort()
xs.index(2) # first index of 2 (ValueError if missing)
xs.count(2)
xs.clear()
- For O(1) push/pop on both ends, use
collections.deque
1.3 Slicing as L-Value
- Slices can appear on the LHS to splice in/delete/replace a range in one step
xs = [1, 2, 3, 4, 5]
xs[1:3] = [20, 30] # [1, 20, 30, 4, 5]
xs[1:3] = [] # [1, 4, 5] (deletion)
xs[::2] = [10, 30, 50] # [10, 4, 30, 1, 50] (extended slice)
del xs[1:3]
- No C++ analog,
std::vectorrequireserase+insert
2. tuple (Immutable Sequence)
t = (1, 2, 3)
t = 1, 2, 3 # parens optional (except in calls / where ambiguous)
len(t)
t[0]
t + (4, 5) # new tuple
- Hashable (if elements are) → can be dict keys / set elements
- Lists cannot
2.1 The Single-Element Tuple Trap
(1) # just int 1 in parentheses
(1,) # 1-tuple
type((1)) # <class 'int'>
type((1,)) # <class 'tuple'>
- The comma makes it a tuple, not the parentheses,
1,is also a 1-tuple
2.2 namedtuple and NamedTuple
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(1, 2)
p.x, p.y # 1, 2
p[0] # 1, still indexable
# Typed version (3.6+, preferred):
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
- For mutable records, prefer
@dataclass
3. Iteration Helpers
for i, x in enumerate(xs): # index + value
...
for a, b in zip(xs, ys): # parallel, stops at shorter
...
for a, b in zip(xs, ys, strict=True): # 3.10+: error on mismatch
...
for x in reversed(xs): # reverse without copying
...
for x in sorted(xs, key=lambda x: x.name):
...
enumerateandzipare lazy, don't materialize a new list
4. Sorting
sort()mutates the list;sorted()returns a new sorted list and accepts any iterable- Sort is stable, order of equal keys preserved
xs.sort(reverse=True)
ys = sorted(words, key=str.lower) # case-insensitive
ys = sorted(items, key=lambda i: (i.team, -i.score)) # tuple key = multi-field
For speed on complex keys, prefer operator.itemgetter/attrgetter over lambdas:
from operator import itemgetter, attrgetter
sorted(records, key=itemgetter("name"))
sorted(people, key=attrgetter("age"))
5. Copying
b = a[:] # shallow copy
b = a.copy() # shallow copy
b = list(a) # shallow copy
import copy
b = copy.deepcopy(a) # recursive copy
b = ais NOT a copy, both names refer to the same list
6. Comprehensions
6.1 List Comprehensions
- Build a list from an iterable with an optional filter
squares = [x * x for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
matrix = [[r * c for c in range(3)] for r in range(3)]
flat = [x for row in matrix for x in row] # nested → flat
- One of Python's most-used idioms; no clean C++ equivalent until C++20 ranges:
// C++20:
auto squares = std::views::iota(0, 10)
| std::views::transform([](int x){ return x*x; });
6.2 Dict and Set Comprehensions
{x: x * x for x in range(5)} # dict
{x % 7 for x in range(20)} # set
6.3 When Not to Use a Comprehension
- Side effects → use a plain
for-loop; comprehensions exist to build a collection
# Bad: comprehension for side effects
[print(x) for x in xs]
# Good: explicit loop
for x in xs:
print(x)
- For large iterables consumed once, prefer a generator expression, same syntax with
(), to avoid materializing the list:
total = sum(x * x for x in range(10**6)) # lazy; constant memory