Skip to main content

Objects/sliceobject.c

cpython 3.14 @ ab2d84fe1023/Objects/sliceobject.c

A PySliceObject stores three arbitrary Python objects: start, stop, and step. Each may be None, signalling that the bound is absent. The None sentinel is preserved through the object lifecycle so that sequences can distinguish a[:] from a[0:len(a)]. Concrete integer bounds are computed lazily by the sequence implementation when it calls the resolution API.

Two resolution functions convert the raw slice fields to Py_ssize_t values clamped to a known length. PySlice_GetIndicesEx (the original API) does both unpacking and clamping in one call and is the form most commonly called from extension code. PySlice_Unpack (added in Python 3.4 / PEP 357) separates unpacking from clamping: it converts None and negative step to concrete values but does not clamp to any length. PySlice_AdjustIndices then takes the unpacked values and clamps them to the actual sequence size, returning the number of elements covered by the slice.

PyEllipsis_Type and the singleton _Py_EllipsisObject live in this file. Ellipsis has no fields beyond the standard object header and exists purely as a sentinel for use in multidimensional indexing.

Map

LinesSymbolRolegopy
1-60_Py_EllipsisObject, PyEllipsis_TypeThe Ellipsis singleton and its type object.objects/slice.go:EllipsisType
60-130PySlice_NewAllocates a PySliceObject; substitutes Py_None for absent bounds.objects/slice.go:NewSlice
130-200PySlice_GetIndicesExOne-call API: unpack None/negative, clamp to length.objects/slice.go:SliceGetIndicesEx
200-270PySlice_Unpack, PySlice_AdjustIndicesTwo-step API: unpack without clamping, then clamp separately (PEP 357).objects/slice.go:SliceUnpack
270-330slice_hashHash of the (start, stop, step) tuple; slices are hashable iff all fields are hashable.objects/slice.go:sliceHash
330-370slice_reprProduces slice(start, stop, step).objects/slice.go:sliceRepr
370-400slice_richcompare, method table, PySlice_TypeEquality by field; method table; type object.objects/slice.go:SliceType

Reading

PySlice_New (lines 60 to 130)

cpython 3.14 @ ab2d84fe1023/Objects/sliceobject.c#L60-130

PySlice_New stores each argument directly. When a bound is passed as NULL from C code, Py_None is substituted so downstream code always sees a valid Python object in each field:

PyObject *
PySlice_New(PyObject *start, PyObject *stop, PyObject *step)
{
if (step == NULL) step = Py_None;
if (start == NULL) start = Py_None;
if (stop == NULL) stop = Py_None;

PySliceObject *obj = PyObject_GC_New(PySliceObject, &PySlice_Type);
if (obj == NULL) return NULL;

obj->step = Py_NewRef(step);
obj->start = Py_NewRef(start);
obj->stop = Py_NewRef(stop);

_PyObject_GC_TRACK(obj);
return (PyObject *)obj;
}

Slices participate in the GC cycle collector because start, stop, and step are arbitrary Python objects that could form cycles.

PySlice_GetIndicesEx (lines 130 to 200)

cpython 3.14 @ ab2d84fe1023/Objects/sliceobject.c#L130-200

The function maps three optional Python objects and a sequence length to four Py_ssize_t values. The algorithm handles None, negative indices, and the special semantics of a negative step (which flips the default start/stop):

int
PySlice_GetIndicesEx(PyObject *_r, Py_ssize_t length,
Py_ssize_t *start, Py_ssize_t *stop,
Py_ssize_t *step, Py_ssize_t *slicelength)
{
PySliceObject *r = (PySliceObject *)_r;

/* step */
if (r->step == Py_None) {
*step = 1;
} else {
if (!_PyEval_SliceIndex(r->step, step)) return -1;
if (*step == 0) {
PyErr_SetString(PyExc_ValueError, "slice step cannot be zero");
return -1;
}
if (*step < -PY_SSIZE_T_MAX) *step = -PY_SSIZE_T_MAX;
}

/* start */
if (r->start == Py_None) {
*start = (*step < 0) ? PY_SSIZE_T_MAX : 0;
} else {
if (!_PyEval_SliceIndex(r->start, start)) return -1;
if (*start < 0) { *start += length; if (*start < 0) *start = (*step < 0) ? -1 : 0; }
else if (*start >= length) *start = (*step < 0) ? length-1 : length;
}

/* stop */
if (r->stop == Py_None) {
*stop = (*step < 0) ? PY_SSIZE_T_MIN : length;
} else {
if (!_PyEval_SliceIndex(r->stop, stop)) return -1;
if (*stop < 0) { *stop += length; if (*stop < 0) *stop = (*step < 0) ? -1 : 0; }
else if (*stop >= length) *stop = (*step < 0) ? length-1 : length;
}

/* length of the slice */
if ((*step < 0 && *stop >= *start) || (*step > 0 && *start >= *stop))
*slicelength = 0;
else if (*step < 0)
*slicelength = (*stop - *start + 1) / (*step) + 1;
else
*slicelength = (*stop - *start + (*step - 1)) / (*step);
return 0;
}

PySlice_Unpack and PySlice_AdjustIndices (lines 200 to 270)

cpython 3.14 @ ab2d84fe1023/Objects/sliceobject.c#L200-270

The two-step API introduced by PEP 357 separates concerns. PySlice_Unpack resolves None to sentinel extremes (PY_SSIZE_T_MIN / PY_SSIZE_T_MAX) without clamping. This lets callers like list.__getitem__ defer the clamp until the list length is known:

int
PySlice_Unpack(PyObject *_r,
Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step)
{
PySliceObject *r = (PySliceObject *)_r;
if (r->step == Py_None) {
*step = 1;
} else {
if (!_PyEval_SliceIndex(r->step, step)) return -1;
if (*step == 0) {
PyErr_SetString(PyExc_ValueError, "slice step cannot be zero");
return -1;
}
if (*step < -PY_SSIZE_T_MAX) *step = -PY_SSIZE_T_MAX;
}
if (r->start == Py_None)
*start = (*step < 0) ? PY_SSIZE_T_MAX : 0;
else if (!_PyEval_SliceIndex(r->start, start)) return -1;

if (r->stop == Py_None)
*stop = (*step < 0) ? PY_SSIZE_T_MIN : PY_SSIZE_T_MAX;
else if (!_PyEval_SliceIndex(r->stop, stop)) return -1;
return 0;
}

PySlice_AdjustIndices then clamps *start and *stop to [0, length] (or [-1, length-1] for a negative step) and returns the number of items covered:

Py_ssize_t
PySlice_AdjustIndices(Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop,
Py_ssize_t step)
{
/* Clamp start */
if (*start < 0) { *start += length; if (*start < 0) *start = (step < 0) ? -1 : 0; }
else if (*start >= length) *start = (step < 0) ? length-1 : length;
/* Clamp stop */
if (*stop < 0) { *stop += length; if (*stop < 0) *stop = (step < 0) ? -1 : 0; }
else if (*stop >= length) *stop = (step < 0) ? length-1 : length;
/* Compute slice length */
if ((step < 0 && *stop >= *start) || (step > 0 && *start >= *stop))
return 0;
if (step < 0)
return (*stop - *start + 1) / step + 1;
else
return (*stop - *start + step - 1) / step;
}