Objects/rangeobject.c
range is a lazy, immutable arithmetic sequence. Every operation — length,
indexing, slicing, membership — is computed directly from start, stop, and
step with no backing array. CPython 3.x ranges hold arbitrary Python
integers, not C longs, so all arithmetic uses PyLong operations.
Map
| Lines | Symbol | Role |
|---|---|---|
| 1–60 | rangeobject layout | start, stop, step, length fields (all PyObject *) |
| 61–180 | range_new | Constructor; validates args, normalises empty range |
| 181–230 | compute_range_length | Integer length formula with overflow guard |
| 231–290 | range_length | __len__; delegates to compute_range_length |
| 291–360 | range_item | __getitem__ by integer index; negative index wrap |
| 361–460 | range_slice | __getitem__ by slice; returns a new range |
| 461–560 | range_contains | O(1) membership test via modular arithmetic |
| 561–700 | range_iter / rangeiterobject | Fast iterator with C-long fast path |
| 701–1100 | Remaining methods | __repr__, __reduce__, count, index, __reversed__ |
Reading
range_new — empty-range normalisation
When step is positive but stop <= start, or step is negative but
stop >= start, the range is empty. range_new detects this and stores a
canonical empty range rather than leaving inconsistent start/stop values
that would confuse length arithmetic later.
// CPython: Objects/rangeobject.c:95 range_new
static PyObject *
range_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
...
/* Validate step != 0 */
cmp = PyObject_RichCompareBool(step, _PyLong_Zero, Py_EQ);
if (cmp > 0) {
PyErr_SetString(PyExc_ValueError,
"range() arg 3 must not be zero");
goto error;
}
...
}
compute_range_length — overflow-safe length
The mathematical length of range(start, stop, step) is
max(0, ceil((stop - start) / step)). Because start, stop, and step
are arbitrary Python ints, the subtraction and division cannot overflow, but
the result must still fit in Py_ssize_t before it can be returned as a
sequence length.
// CPython: Objects/rangeobject.c:195 compute_range_length
static PyObject *
compute_range_length(PyObject *start, PyObject *stop, PyObject *step)
{
/* length = (stop - start + step + (-1 if step>0 else 1)) / step */
PyObject *tmp1, *tmp2, *result;
int cmp_result;
cmp_result = PyObject_RichCompareBool(step, _PyLong_Zero, Py_GT);
...
tmp1 = PyNumber_Subtract(stop, start);
...
result = PyNumber_FloorDivide(tmp2, step);
...
return result;
}
range_item — index arithmetic
range_item multiplies the normalised index by step, adds start, and
returns the result as a new Python int. Negative indices are wrapped by adding
length before the multiply.
// CPython: Objects/rangeobject.c:312 range_item
static PyObject *
range_item(PyRangeObject *r, Py_ssize_t i)
{
Py_ssize_t len = PyLong_AsSsize_t(r->length);
if (i < 0)
i += len;
if (i < 0 || i >= len) {
PyErr_SetString(PyExc_IndexError,
"range object index out of range");
return NULL;
}
/* result = start + step * i */
...
}
range_contains — O(1) membership
Membership is tested without iterating: a value v is in
range(start, stop, step) when it is reachable by the step arithmetic.
The check is: v >= start (for positive step), (v - start) % step == 0,
and the quotient is less than length. For non-integer arguments it falls
back to the generic sequence scan.
// CPython: Objects/rangeobject.c:493 range_contains_long
static int
range_contains_long(PyRangeObject *r, PyObject *ob)
{
PyObject *tmp1, *tmp2;
int result = 0;
/* Check ob >= start (positive step) or ob <= start (negative step) */
...
tmp1 = PyNumber_Subtract(ob, r->start);
tmp2 = PyNumber_Remainder(tmp1, r->step);
result = PyObject_RichCompareBool(tmp2, _PyLong_Zero, Py_EQ);
...
return result;
}
gopy notes
- All five fields (
start,stop,step,length, and the iterator'sindex) arePyObject *in CPython because they can be arbitrarily large ints. A Go port usingint64would silently truncate large ranges; use*big.Intor the gopyobjects.Intwrapper. compute_range_lengthshould be ported as a standalone helper tested independently before any of the sequence methods call it.range_iterhas an internal C-long fast path that bypasses Python-int arithmetic whenstart,step, andlengthall fit inlong. Port that optimisation last, after the slow path is verified correct.range_containsis the most frequently called method in numeric loops; get the modular-arithmetic edge cases (negative step, zero delta) right before marking it done.
CPython 3.14 changes
- The
rangeiterobjectgained__copy__/__deepcopy__so thatcopy.copy(iter(range(n)))returns an independent iterator at the same position, consistent with other iterator types. range.__class_getitem__now returns atypes.GenericAlias, allowingrange[int]in type annotations without raisingTypeError.- Internal use of
_PyLong_IsZeroand the new small-int fast paths inObjects/longobject.cmeans thatrange(0, n, 1)construction avoids several heap allocations compared with 3.12.