Python slice.indices:反向切片已经标准化,为什么重新组成切片却变成空列表

前天 3阅读

实现自定义序列时,slice.indices能把省略值与越界边界整理成三个整数。顺手把结果重新塞回slice,看似得到了等价切片,反向遍历却变成空列表。原因是这三个整数已经进入标准化索引空间,再当作原始切片参数解释,含义可能变化。

下面在Linux、CPython 3.12.14实跑,只操作短列表。保存为demo.py,运行python3 demo.py。原始请求从尾到头读取五项;程序同时打印标准化参数、索引序列、直接切片和重组结果。末尾再用空序列、单项序列与越界等情况核对实现。

Python slice.indices:反向切片已经标准化,为什么重新组成切片却变成空列表

AI模型生成概念插图:一列图块向左遍历,终止标记位于首项之前,表示边界位置与实际元素索引需要分开;不是调试器截图。

完整程序与本地结果

完整可运行程序

values = [10, 20, 30, 40, 50]
request = slice(None, None, -1)
normalized = request.indices(len(values))
positions = list(range(*normalized))
direct = values[request]
rebuilt = values[slice(*normalized)]
by_positions = [values[index] for index in positions]
assert normalized == (4, -1, -1)
assert positions == [4, 3, 2, 1, 0]
assert direct == by_positions == [50, 40, 30, 20, 10]
assert rebuilt == []
print("normalized:", normalized)
print("positions:", positions)
print("original slice:", direct)
print("rebuilt slice:", rebuilt)
print("index traversal:", by_positions)

cases = [
    slice(None, None, -1),
    slice(99, -99, -2),
    slice(-99, 99, 2),
    slice(2, 2),
]
checked = 0
for data in ([], [7], values):
    for selection in cases:
        result = [data[i] for i in range(*selection.indices(len(data)))]
        assert result == data[selection]
        checked += 1
print("checked slice cases:", checked)
try:
    slice(None, None, 0).indices(len(values))
except ValueError:
    print("zero step: ValueError")
else:
    raise AssertionError("a zero step is invalid")

本次实际输出(以下为结果,不是程序)

normalized: (4, -1, -1)
positions: [4, 3, 2, 1, 0]
original slice: [50, 40, 30, 20, 10]
rebuilt slice: []
index traversal: [50, 40, 30, 20, 10]
checked slice cases: 12
zero step: ValueError

标准化的负一是停止边界

五项序列得到(4, -1, -1)。交给range后,依次产生4、3、2、1、0,在达到-1前停止。这里的-1表示首项左侧的边界,不是要读取最后一项;range不会根据序列长度再次折算负数。

因此index traversal与original slice都得到[50, 40, 30, 20, 10]。标准化结果适合告诉自定义遍历逻辑从哪里开始、在哪里停止、每次走多远,并不保证重新构造出的原始切片仍然等价。原始请求与遍历计划是两种不同表示。

重新构造slice会再次解释负索引

把三个整数放进slice(4, -1, -1),列表按普通切片规则把显式stop=-1解释成末项位置,也就是4。起点与不包含的终点相同,结果为空。负步长时,原始stop=None与显式stop=-1不能随意互换。

如果仍要对列表切片,直接保留request;如果要自己实现逐项读取,就消费range(*request.indices(length))。不要在两种表示之间重复标准化,也不要为了让一例通过,把所有负一都替换成None,否则其他明确要求排除末项的请求可能被改变。

边界计划只对应计算时的长度

indices结果依赖传入长度。程序对三种长度、四种请求共做十二次比较,并断言逐项实现与内置切片相同。动态序列若允许在计算长度和读取之间变化,仍需要同步或快照约定;这个方法本身不会锁定内容。

零步长会抛ValueError,不能当作普通空结果吞掉。空输入、两端越界、正负步长和空区间都适合作为回归样本。排查分页容器或自定义序列时,同时记录原始slice、计算长度与实际索引,比只看最后的空列表更容易找出二次解释的问题。

参考资料

资料核验日期:2026年10月2日。以上输出对应文中固定输入和明确运行版本,本地执行退出码为0。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。