Objects/sliceobject.c: Slice and Ellipsis Objects
Objects/sliceobject.c covers two distinct things: the Ellipsis singleton
(...) and the slice built-in type. The file is mid-sized at ~400 lines but
punches above its weight because PySlice_Unpack and PySlice_AdjustIndices
are called by every sequence that supports extended slicing.
Map
| Lines | Symbol | Role |
|---|---|---|
| 1-40 | Py_Ellipsis | Singleton object and PyEllipsis_Type |
| 41-100 | PySlice_New | Allocate PySliceObject, store start/stop/step as-is (may be None) |
| 101-200 | PySlice_Unpack | Resolve None to defaults, clamp integers to [PY_SSIZE_T_MIN, PY_SSIZE_T_MAX] |
| 201-280 | PySlice_AdjustIndices | Normalize start/stop to [0, length], return step count |
| 281-330 | slice_indices | Python-level slice.indices(n) method |
| 331-360 | slice_hash | Hash (start, stop, step) tuple hash |
| 361-400 | Type object, __reduce__ | PySlice_Type slots and pickle support |
Reading
PySliceObject and PySlice_Unpack
PySliceObject stores start, stop, and step as raw Python objects.
None is valid and means "use the default". PySlice_Unpack converts them to
C Py_ssize_t values with explicit clamping so that callers never have to
guard against overflow:
int
PySlice_Unpack(PyObject *_r,
Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step)
{
/* step */
if (r->step == Py_None) {
*step = 1;
} else {
*step = _PyEval_SliceIndex(r->step);
if (*step == 0) { PyErr_SetString(...); return -1; }
/* clamp to avoid overflow in AdjustIndices */
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 {
*start = _PyEval_SliceIndex(r->start);
/* no further clamping needed; AdjustIndices handles bounds */
}
/* stop: symmetric with start */
...
return 0;
}
The only hard clamp is step against -PY_SSIZE_T_MAX (not MIN) to prevent
-step from overflowing during length calculations in AdjustIndices.
PySlice_AdjustIndices
After Unpack yields raw signed integers, AdjustIndices normalises them
against the actual sequence length and returns the number of elements the slice
covers:
Py_ssize_t
PySlice_AdjustIndices(Py_ssize_t length,
Py_ssize_t *start, Py_ssize_t *stop,
Py_ssize_t step)
{
if (*start < 0) { *start += length; if (*start < 0) *start = (step<0)?-1:0; }
else if (*start >= length) *start = (step<0) ? length-1 : length;
if (*stop < 0) { *stop += length; if (*stop < 0) *stop = (step<0)?-1:0; }
else if (*stop >= length) *stop = (step<0) ? length-1 : length;
if (step < 0) {
if (*stop < *start) return (*start - *stop - 1) / (-step) + 1;
} else {
if (*start < *stop) return (*stop - *start - 1) / step + 1;
}
return 0;
}
The division (range - 1) / abs(step) + 1 is the ceiling-division form that
counts items correctly for any step magnitude without floating-point.
Ellipsis singleton and slice_hash
Py_Ellipsis is a true singleton: PyEllipsis_Type has no fields beyond the
base object header. Equality is always identity. Repr is the string "Ellipsis"
and the literal ... in Python source maps directly to this object.
slice_hash delegates to PyObject_Hash on a temporary (start, stop, step)
tuple. Slices were unhashable before 3.12. The 3.12 change made them hashable
only when all three components are hashable, which required no struct change,
just wiring up the tp_hash slot:
static Py_hash_t
slice_hash(PySliceObject *v)
{
PyObject *t = PyTuple_Pack(3, v->start, v->stop, v->step);
if (t == NULL) return -1;
Py_hash_t h = PyObject_Hash(t);
Py_DECREF(t);
return h;
}
gopy notes
objects/slice.gomirrorsPySliceObjectasSlice{Start, Stop, Step Object}.UnpackandAdjustIndicesare ported as methods(*Slice).Unpack(length int64)returning(start, stop, step, count int64, err error)in one call, combining both CPython functions to avoid the intermediate struct.- The
Ellipsissingleton isobjects.Ellipsis, a package-level*EllipsisObjectinitialised ininit(). Identity comparison uses pointer equality, matching CPython behaviour. slice_hashis ported by hashing a[3]Objectarray via the standardobjects.TupleHashhelper rather than allocating a real tuple.- 3.14 change:
_PyEval_SliceIndexwas madestatic inlineand moved into the headerInclude/internal/pycore_sliceobject.h. The gopy equivalent (sliceIndexinobjects/slice.go) was already a private function, so no public API change is required.