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
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-60 | _Py_EllipsisObject, PyEllipsis_Type | The Ellipsis singleton and its type object. | objects/slice.go:EllipsisType |
| 60-130 | PySlice_New | Allocates a PySliceObject; substitutes Py_None for absent bounds. | objects/slice.go:NewSlice |
| 130-200 | PySlice_GetIndicesEx | One-call API: unpack None/negative, clamp to length. | objects/slice.go:SliceGetIndicesEx |
| 200-270 | PySlice_Unpack, PySlice_AdjustIndices | Two-step API: unpack without clamping, then clamp separately (PEP 357). | objects/slice.go:SliceUnpack |
| 270-330 | slice_hash | Hash of the (start, stop, step) tuple; slices are hashable iff all fields are hashable. | objects/slice.go:sliceHash |
| 330-370 | slice_repr | Produces slice(start, stop, step). | objects/slice.go:sliceRepr |
| 370-400 | slice_richcompare, method table, PySlice_Type | Equality 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;
}