What Python is exactly doing while slicing a list is coded in sliceobject.c
file of Python source code. If you are not in trouble to clearly see how it comes that none of the lines of code below gives an Exception or Error or you are not surprised by the outcome of slicing:
assert [0,1,2,3][-23: 32] == [0,1,2,3]
assert [0,1,2,3][ 23: 32] == []
assert [0,1,2,3][-32:-23] == []
assert [0,1,2,3][ 23:-23:-1] == [3,2,1,0]
# ---
assert [0,1,2,3][ -1: 3:-1] == []
assert [0,1,2,3][ -1: 2:-1] == [3]
assert [0,1,2,3][ -1: 1:-1] == [3,2]
assert [0,1,2,3][ -1: 0:-1] == [3,2,1]
assert [0,1,2,3][ -1:-1:-1] == []
assert [0,1,2,3][ -1:-2:-1] == [3]
assert [0,1,2,3][ -1:-3:-1] == [3,2]
assert [0,1,2,3][ -1:-4:-1] == [3,2,1]
assert [0,1,2,3][ -1:-5:-1] == [3,2,1,0]
# ---
assert [0,1,2,3][321:-123: 1] == []
assert [0,1,2,3][321:-123:-1] == [3,2,1,0]
assert [0,1,2,3][-123:321:-1] == []
# ---
assert [0,1][None:None][None:][::][:] == [0,1]
there is good chance that you had already well understood how Python slicing work.
If you were surprised by some of the given examples, looking into the Python source code may help to resolve the confusion. I have rewritten the C-function returning start, stop, step values for range()
which can then be used to create a slice of a listL
with:
[ listL[indx] for indx in range(start,stop, step) ]
in Python and provided it below. It can be studied in detail to get some insight into the mechanisms behind slicing a list. I hope it will help you to gain understanding of the at the first glance maybe surprising or hard to grasp results with negative step and index while slicing a list.
By the way: if you ever wondered how to reach the first element of a list while using a negative step, here some options how it can be done:
listL = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert listL[3: 2: -1] == [3]
assert listL[3: 1: -1] == [3, 2]
assert listL[3: 0: -1] == [3, 2, 1]
...
assert listL[3: -11: -1] == [3, 2, 1, 0]
assert listL[3:None: -1] == [3, 2, 1, 0]
assert listL[3::-1] == [3, 2, 1, 0]
assert listL[3:-321: -1] == [3, 2, 1, 0]
Below the Python script code equivalent to a part of the C-code in sliceobject.c file which handles slicing. Notice that missing values in [::] are turned into None for the evaluation of the slice indices. I have provided some comments in the code to help grasping what and why:
# Python-3.9.10_srcCode/Objects/sliceobject.c:354:
# evaluate_slice_index(PyObject *v)
def evaluate_slice_index(v):
if v is None:
return None
if type(v) is int:
return v
if '__index__' in dir(v):
return v.__index__()
else:
raise TypeError(
"slice indices must be integers or None or have an __index__ method")
#:def
# Python-3.9.10_srcCode/Objects/sliceobject.c:372:
# _PySlice_GetLongIndices(PySliceObject *self, PyObject *length,
def _PySlice_GetLongIndices(objSlice, intLength):
'''
Compute slice indices given a slice and length.
Assumes that intLength is a nonnegative integer
'''
start=None; stop=None; step=None
upper=None; lower=None
# Convert step to an integer; raise for zero step.
if (objSlice.step is None):
step = 1
step_is_negative = False
else:
step = evaluate_slice_index(objSlice.step)
if ( step == 0 ):
raise ValueError( "slice step cannot be zero" )
if step < 0:
step_is_negative = True
else:
step_is_negative = False
# Find lower and upper bounds for start and stop.
if (step_is_negative):
lower = -1
upper = intLength + lower
else:
lower = 0
upper = intLength
# ^-- this is the 'trick' to cope with the stop value for range()
# providing values not including the stop value.
# Compute start:
if (objSlice.start == None):
start = upper if step_is_negative else lower
else:
start = evaluate_slice_index(objSlice.start)
if ( start < 0):
start = start + intLength
if start < lower:
start = lower
# ^-- explains how it comes, that any values
# for slice indices are OK.
else:
if start > upper:
start = upper
# ^-- explains how it comes, that any values
# for slice indices are OK.
# ^-- this is the 'trick' to get start from deliberate value
# into the range within the scope of valid list indices.
# The positive/negative step value case is already handled
# by the choice of values for lower and upper.
# Compute stop:
if (objSlice.stop == None):
stop = lower if step_is_negative else upper
else:
stop = evaluate_slice_index(objSlice.stop);
if ( stop < 0):
stop = stop + intLength
if (stop < lower):
stop = lower;
else:
if (stop > upper):
stop = upper;
# ^-- this is the 'trick' to get stop from deliberate value
# into the range within the scope of valid stop indices.
# The positive/negative step value case is already handled
# by the choice of values for lower and upper.
return (start, stop, step) # for range(start,stop,step) which can
# be used to obtain the slice from a list using:
# [ theList[index] for index in range(start, stop, step ]
#:def
# Let's check if the code above does the same as the .indices() function
# of the slice object:
listL = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for start in list(range(-3*len(listL), 3*len(listL))) + [None]:
for stop in list(range(-3*len(listL), 3*len(listL))) + [None]:
for step in list(range(-2*len(listL), 2*len(listL))) + [None]:
objSlice = slice(start,stop,step)
try:
py = objSlice.indices(intLength)
except:
try:
Py = _PySlice_GetLongIndices(objSlice, intLength)
echo(" STOP: difference in Exceptions")
import sys; sys.exit()
except:
continue
Py = _PySlice_GetLongIndices(objSlice, intLength)
assert py == Py
listL = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
intLength = len(listL)
# If you ever wondered how to get the first element of a list with
# a negative step, here two options how it can be done:
assert listL[3: 2:-1] == [3]
assert listL[3: 1:-1] == [3, 2]
assert listL[3: 0:-1] == [3, 2, 1]
...
assert listL[3: -11:-1] == [3, 2, 1, 0]
assert listL[3:None:-1] == [3, 2, 1, 0]
assert listL[3::-1] == [3, 2, 1, 0]
assert listL[3:-321:-1] == [3, 2, 1, 0]
# Both [:] and [::] have the same effect:
assert listL[:] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert listL[::] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
objSlice = slice(None,None,None) # equivalent to [::] and [:]
assert listL[objSlice] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# also all the over variants lead to same result:
assert listL[None:] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert listL[None:None] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert listL[None:None:None] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert [ listL[indx] for indx in range(2,1,1) ] == []
# ^-- Another 'trick' of slicing is to start with an empty list.
# If range(start,stop,step) don't deliver any value, an empty
# list without any added list elements is returned as result of
# slicing. Exceptions are raised only on zero step value and
# inappropriate types for start or stop values of the slice.