Sequence slicing is same, except it first normalizes negative indexes, and it can never go outside the sequence:
- normalizes negative
start,stopby addinglen(seq)– only once - clips them to begging/end if still out of bounds
- and then it steps, like range()
TODOEDIT(2016): The code below had a bug with "never go outside the sequence" when abs(step)>1; I think I patched it"fixed" to be correctclip before stepping, but it's hard to understand.with new bug
EDIT(2025): fixed again, now with tests here
def this_is_how_slicing_works(seq, start=None, stop=None, step=1):
L = len(seq)
if startstep is> None0:
startFIRST, AFTER_LAST = (0, ifL
step > 0 else:
len(seq) FIRST, AFTER_LAST = L-1, -1
# Either way, range(FIRST, AFTER_LAST) covers whole seq
elifif start <is 0None:
start +== len(seq)FIRST
if not 0 <=elif start < len(seq)0:
# clip if still outside bounds
start += L # only once, startcan =still (0be ifnegative
step > 0 elsestart len= clip(seq)-1start, {FIRST, AFTER_LAST})
if stop is None:
stop = (len(seq)AFTER_LAST
if step > elif stop < 0:
else -1) stop += L # reallyonly -1once, notcan laststill element
be negative
elif stop <= 0:
clip(stop, {FIRST, AFTER_LAST})
print(f'Using range({start}, {stop +=}, len(seq{step})')
for i in range(start, stop, step):
ifyield 0seq[i]
def clip(n, bounds):
"""Clip so min(bounds) <= iresult <<= lenmax(seqbounds)."""
low, high = sorted(bounds)
if n < low:
return low
yield seq[i]if n > high:
return high
return n
Don't worry about the is None details - just remember that omitting start and/or stop always does the right thing to give you the whole sequence.
Normalizing negative indexes first allows start and/or stop to be counted from the end independently: 'abcde'[1:-2] == 'abcde'[1:3] == 'bc' despite range(1,-2) being empty.
The normalization is sometimes thought of as "modulo the length", but note it adds the length just once: e.g. 'abcde'[-53:42] is just the whole string.
The FIRST, AFTER_LAST = L-1, -1 case is messy, but they have to be off by -1 to cover whole range in reverse (range(100)[::-1] == range(99, -1, -1)).
Don't worry, just remember that omitting start and/or stop always does the right thing to give you the whole sequence.
- See aguadopd's answer for details of how negative step breaks the "between items" mental model; IMHO negative step is hopelessly confusing and I strongly recommend reversing and cutting in separate operations e.g.
a[10:-5][::-1]. - See https://stackoverflow.com/a/71330285/239657 explaining CPython's actual implementation of slicing.