Python Lists, Tuples, and Comprehensions


  • Description: list (mutable) vs tuple (immutable), slicing semantics, sorting with key=, 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)

  • 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::vector requires erase + 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):
    ...
  • enumerate and zip are 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 = a is 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