Skip to main content

Objects/tupleobject.c (part 5)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/tupleobject.c

This annotation covers tuple search and hashing. See objects_tupleobject4_detail for tuple.__new__, PyTuple_New, PyTuple_Pack, and the ob_item layout.

Map

LinesSymbolRole
1-80tuple.__hash__Hash a tuple by combining element hashes
81-180tuple.__contains__Linear scan for membership
181-280tuple.countCount occurrences of a value
281-400Tuple interningSmall empty and one-element tuples are cached

Reading

tuple.__hash__

// CPython: Objects/tupleobject.c:380 tuplehash
static Py_hash_t
tuplehash(PyTupleObject *v)
{
/* Use xxHash-based algorithm for tuples of any length.
Each element's hash is mixed in using a multiply-xor-rotate scheme. */
Py_ssize_t len = Py_SIZE(v);
Py_uhash_t acc = _PyHASH_XXROTATE(_PyHASH_MULTIPLIER);
for (Py_ssize_t i = 0; i < len; i++) {
Py_uhash_t lane = PyObject_Hash(v->ob_item[i]);
if (lane == (Py_uhash_t)-1) return -1;
acc += lane * _PyHASH_MULTIPLIER;
acc = _PyHASH_XXROTATE(acc);
acc *= _PyHASH_XXROTATE_MUL;
}
acc += len ^ (_PyHASH_MULTIPLIER ^ 3527539UL);
return (acc == (Py_uhash_t)-1) ? -2 : acc;
}

Tuples are hashable (unlike lists) because they are immutable. hash((1, 2, 3)) mixes each element's hash in order. The final result depends on both values and positions: hash((1, 2)) != hash((2, 1)).

Tuple interning

// CPython: Objects/tupleobject.c:80 tuple free list
/* CPython maintains per-size free lists for tuples of size 0..19.
tuple.__new__(size) checks the free list before calling malloc.
Empty tuple () is a singleton: _PyTuple_EMPTY. */
static PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
static int numfree[PyTuple_MAXSAVESIZE];

The empty tuple () is a singleton: () is () returns True. Tuples of size 1-19 are pooled in per-size free lists of up to 2000 items each. This dramatically reduces allocation overhead for tuple-heavy code.

tuple.count

// CPython: Objects/tupleobject.c:480 tuplecount
static PyObject *
tuplecount(PyTupleObject *self, PyObject *v)
{
Py_ssize_t count = 0;
for (Py_ssize_t i = 0; i < Py_SIZE(self); i++) {
int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
if (cmp > 0) count++;
else if (cmp < 0) return NULL; /* Error in __eq__ */
}
return PyLong_FromSsize_t(count);
}

(1, 2, 1, 3).count(1) returns 2. The comparison uses PyObject_RichCompareBool which calls __eq__, so custom types are supported.

gopy notes

tuple.__hash__ is objects.TupleHash in objects/tuple.go. It uses the same multiply-xor-rotate algorithm as CPython for compatibility. Tuple free lists are implemented as Go sync.Pools per size. The empty tuple singleton is objects.EmptyTuple.