Python 扩展切片赋值:普通切片能缩短列表,为什么隔一项替换却必须数量相同

昨天 3阅读

把列表的一段内容替换成一个值,长度可以变短;把切片改成每隔一项,赋同样一个值却抛ValueError。两种写法选择的目标不同:连续切片可以替换一段区域,而步长不为一的扩展切片赋值,需要为每个选中位置提供对应元素。

本例在Linux、CPython 3.12.14实跑。保存为demo.py,运行python3 demo.py。每组实验都从自己的短列表开始,只修改内存数据;程序覆盖正步长、负步长、数量不匹配、删除和零步长,避免把一种写入规则推广到所有切片操作。

Python 扩展切片赋值:普通切片能缩短列表,为什么隔一项替换却必须数量相同

AI生成的概念示意图:三个选中位置各自对应一枚替换块,旁边的一枚块无法自然对应三个位置;不是列表内存布局图。

完整程序与本地结果

完整可运行程序

ordinary = list(range(6))
ordinary[1:4] = [9]
assert ordinary == [0, 9, 4, 5]
print("ordinary slice resizes:", ordinary)

stepped = list(range(6))
positions = list(range(len(stepped)))[::2]
stepped[::2] = [10, 20, 30]
assert stepped == [10, 1, 20, 3, 30, 5]
print("selected positions:", positions)
print("equal-length replacement:", stepped)
before = stepped.copy()
try:
    stepped[::2] = [99]
except ValueError:
    print("short replacement: ValueError")
else:
    raise AssertionError("length mismatch accepted")
assert stepped == before
print("list unchanged after mismatch:", stepped == before)

backward = list(range(6))
backward[::-2] = [50, 30, 10]
assert backward == [0, 10, 2, 30, 4, 50]
print("negative-step replacement:", backward)
removed = list(range(6))
del removed[::2]
assert removed == [1, 3, 5]
print("extended-slice deletion:", removed)
try:
    removed[::0] = []
except ValueError:
    print("zero step: ValueError")
else:
    raise AssertionError("zero step accepted")

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

ordinary slice resizes: [0, 9, 4, 5]
selected positions: [0, 2, 4]
equal-length replacement: [10, 1, 20, 3, 30, 5]
short replacement: ValueError
list unchanged after mismatch: True
negative-step replacement: [0, 10, 2, 30, 4, 50]
extended-slice deletion: [1, 3, 5]
zero step: ValueError

连续片段可以被不同长度的内容替换

ordinary最初是零到五,索引一到四之前的连续片段包含一、二、三。把它替换成单元素列表九,结果成为零、九、四、五,长度由六变四。未选中的尾部元素随连续区域缩短而前移,这正是普通切片赋值允许的行为。

因此普通切片可以表达插入、删去或替换一段内容。调用者如果还保存了旧的数字索引,就应意识到后续位置可能变化。列表对象仍是同一个,并不意味着原来的每个索引还指向同样的业务记录。

非一步长赋值要求选中位置逐项对应

stepped的切片取每隔一项,目标位置是零、二、四,合计三处。提供十、二十、三十后,结果中这三个位置各自得到一个新值,索引一、三、五保留原内容,整体长度仍为六。程序把目标索引另外打印出来,便于和结果逐项对照。

这里没有广播规则。将单元素列表九十九交给同一个三位置切片,并不会自动把九十九复制三次,而是抛ValueError。若业务确实要求重复同一个值,应显式构造三个替换元素,并另外考虑其中若是可变对象,是否允许共享同一引用。

错误样本保留原列表,但不要忽略输入副作用

赋值前先保存before,捕获数量不匹配后比较stepped与before,输出True。这验证了本例这种普通列表右侧输入在失败时没有留下部分替换。记录异常类型与修改结果,比只判断“没有崩溃”更能说明失败边界。

但这个观察不代表所有表达式都能回滚副作用。如果右侧是一个生成器或函数调用,它可能在产生替换项时已经记录日志、更新其它状态或抛出异常。切片赋值对目标列表的约束,不会自动撤销右侧求值过程中的外部行为。

负步长按访问顺序接收替换值

backward从列表末尾每隔一项,依次选中索引五、三、一。提供五十、三十、十后,最终从左到右看到零、十、二、三十、四、五十。替换值按切片的访问方向对应位置,并不是总从最左边选中项开始填写。

处理倒序抽样时,最好先在小样本上打印所选索引,再映射业务值。特别是替换内容已经按某种业务顺序排列时,要确认那个顺序与切片方向一致;数量相同只能保证结构合法,无法证明值已经放在正确的位置。

删除与赋空列表不是同一条规则

del removed[::2]成功删除零、二、四位置,剩下一、三、五,长度确实缩短。扩展切片删除有自己的操作语义,不能据此推断给同一切片赋空列表也应该成功;后者仍是赋值,通常会因选中数量与零不相同而被拒绝。

最后的零步长会抛ValueError,即使右侧也是空列表也一样。零无法描述有效的访问推进方向。若步长来自配置,应在处理前验证,避免把参数错误误报成替换值数量不足,给排查带来不必要的绕路。

本文讨论内建list。其他容器、自定义__setitem__或数组库可能定义不同的形状、广播与写入规则,不能直接照搬结果。针对列表编辑接口,分别验收连续区域长度变化、稀疏位置数量匹配以及负方向顺序,就能把“选哪些位置”和“怎样分配新值”清楚地区分开。

参考资料

资料核验日期:2026年10月2日。以上输出来自固定输入的本地实跑,退出码为0。

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