Inside Python Tuples: How They’re Implemented and Optimized
This article explains Python tuples as immutable list subsets, details their C‑level struct layout, creation and deallocation processes, the internal freelist cache for lengths up to 20, performance advantages over lists, and the special handling of the empty tuple singleton.
Python tuples can be viewed as immutable lists: they do not support element addition, modification, or deletion, which makes them suitable as dictionary keys and set elements because they are hashable.
The underlying C structure is defined in Include/cpython/tupleobject.h as:
typedef struct {
PyObject_VAR_HEAD
PyObject *ob_item[1];
} PyTupleObject;This struct contains reference count, type, size, and a pointer array. Although the array length is declared as 1, it is treated as n for a tuple of length n . The memory occupied by a tuple equals 24 + 8 × length bytes.
Compared with lists, tuples lack an allocated (capacity) field because they are immutable and cannot be resized. The pointer array lives inside the struct, while a list’s pointer array is stored externally and accessed via a secondary pointer.
Tuple creation is performed by PyTuple_New(Py_ssize_t size) in Objects/tupleobject.c. The function checks for a zero size (returning the empty tuple), calls tuple_alloc to obtain memory, initializes all ob_item entries to NULL, tracks the object with the garbage collector, and finally returns the new PyObject *. tuple_alloc first tries to reuse a cached object via maybe_freelist_pop(size). If the freelist is empty, it validates that the requested size does not exceed PY_SSIZE_T_MAX, then allocates a PyTupleObject and a pointer array with PyObject_GC_NewVar. The allocated object’s type is set to &PyTuple_Type and its ob_size to size.
The tuple cache (freelist) is defined by three macros: PyTuple_MAXSAVESIZE = 20 (the number of length categories) PyTuple_NFREELISTS = 20 (the number of separate chains) PyTuple_MAXFREELIST = 2000 (maximum nodes per chain)
Internally a struct _Py_tuple_state holds two arrays: free_list[PyTuple_NFREELISTS] (head pointers) and numfree[PyTuple_NFREELISTS] (node counts). For a tuple of length n (1 ≤ n ≤ 20) the index n‑1 selects the appropriate chain.
When pushing a tuple onto the freelist, ob_item[0] is repurposed as the next‑pointer, the tuple becomes the new head of free_list[index], and numfree[index] is incremented. Popping retrieves the head, restores the next pointer as the new head, decrements the count, and returns the object with an increased reference count.
Deallocation is handled by tupledealloc(PyTupleObject *op). For non‑empty tuples it untracks the object from the GC, decrements reference counts of contained items, and attempts to push the tuple back into the freelist via maybe_freelist_push. If the tuple’s length exceeds 20 or the chain is full, the memory is freed normally. Empty tuples are singletons and are never deallocated.
The empty tuple is a static singleton created at interpreter startup ( tuple_empty) with a reference count of 2^32‑1, guaranteeing it lives for the entire process.
A simple benchmark demonstrates the speed advantage:
from timeit import timeit
t1 = timeit(stmt="x1 = [1, 2, 3, 4, 5]", number=1000000) # ≈0.05 s
t2 = timeit(stmt="x2 = (1, 2, 3, 4, 5)", number=1000000) # ≈0.01 s
print(round(t1, 2))
print(round(t2, 2))Because tuples are created and destroyed frequently—in multiple assignment, function returns, and other language constructs—the large freelist (20 chains × 2000 nodes) reduces allocation overhead dramatically.
This caching technique is often called a “static resource cache”: the underlying pointer array is retained after deallocation, allowing subsequent allocations of the same size to reuse the memory quickly.
For completeness, the article notes that the tuple freelist previously contained a bug that the author fixed; details are linked in the original source.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Satori Komeiji's Programming Classroom
Python and Rust developer; I write about any topics you're interested in. Follow me! (#^.^#)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
