Lists & Tuples: Memory Layout, Slicing & Performance
Lists and tuples are the foundational sequential data structures in Python. Learn how CPython implements lists as dynamic arrays with amortized O(1) growth, why immutable tuples use less memory and support hash keys, and how to harness advanced slicing and structured unpacking.
1. How CPython Implements Lists (Dynamic Over-Allocation)
In CPython, a list is not a linked list; it is a contiguous dynamic array of pointersto other PyObject instances on the heap. When appending elements, resizing the array on every insertion would result in terrible O(N^2) performance.
Instead, CPython uses an over-allocation formula (approx. new_allocated = (size + (size >> 3) + 6) & ~3). This ensures that list.append() achieves an amortized O(1) constant time complexity!
import sys
# Observe dynamic over-allocation in real time
items = []
print(f"Empty list initial bytes: {sys.getsizeof(items)}")
for i in range(12):
items.append(i)
# Notice bytes jump in discrete chunks (over-allocation) rather than per element!
print(f"Length: {len(items):2d} | Allocated Memory: {sys.getsizeof(items):3d} bytes")2. List vs Tuple: Architectural Differences
| Metric | Python List (list) | Python Tuple (tuple) |
|---|---|---|
| Mutability | Mutable (can append, remove, sort in-place) | Immutable (fixed size and contents at creation) |
| Memory Footprint | Larger (includes over-allocated spare slots) | Compact (exact memory allocated, zero spare slots) |
| Hashability | Unindexable (cannot be a dict key or set element) | Hashable (can be used as a dictionary key if contents are hashable) |
| Primary Purpose | Homogeneous collections of dynamic items | Heterogeneous fixed records (e.g. coordinates (x, y, z)) |
3. Advanced Slicing Syntax: [start:stop:step]
Slicing allows you to extract sub-sequences cleanly. Slices are half-open intervalsincluding start up to but excluding stop:
data = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
print(data[2:6]) # [20, 30, 40, 50] (Elements from index 2 to 5)
print(data[::2]) # [0, 20, 40, 60, 80] (Every 2nd element)
print(data[::-1]) # [90, 80, 70, 60, 50, 40, 30, 20, 10, 0] (Reverse list)
print(data[-3:]) # [70, 80, 90] (Last 3 elements)4. Self-Documenting Data Records with typing.NamedTuple
Instead of accessing tuple elements using error-prone integer indices (record[0], record[1]), use typing.NamedTuple to create memory-efficient, self-documenting data classes:
from typing import NamedTuple
class GeoCoordinate(NamedTuple):
latitude: float
longitude: float
elevation_m: float = 0.0
coord = GeoCoordinate(latitude=37.7749, longitude=-122.4194)
# Access by readable attribute name OR by index!
print("Latitude:", coord.latitude)
print("Index 0:", coord[0])
# Unpacking works seamlessly
lat, lon, elev = coord
print(f"Unpacked location: {lat}, {lon}")Explore Related Tools
Hand-picked utilities and calculators related to this tool.
