
先给你讲个真事儿。
去年我写一个数据清洗脚本,处理一批用户提交的表格数据。每一行是一个用户的多个电话号码,格式不规范,有的有区号,有的没有,有的用斜杠分隔,有的用逗号。
我的思路很简单:先把每一行按分隔符拆成一个列表,然后统一格式化。
原始数据大概长这样:
raw_data = [
["010-88888888", "010-99999999"], # 固定电话
["13800138000"], # 手机号
["010-66666666", "020-77777777", "13900139000"], # 混搭
]我要把每个列表里带区号的固话统一格式,手机号保留原样。这逻辑不复杂,我写了段代码循环处理:
for row in raw_data:
for i in range(len(row)):
if "-" in row[i]:
# 把 "010-88888888" 变成 ["010", "88888888"],再合并成标准格式
area, number = row[i].split("-")
row[i] = f"({area}){number}"运行一下,第一行变成了['(010)88888888', '(010)99999999'],没问题。
但马上需求变了,领导说:手机号也要拆一下,按运营商分类,移动的放前面,联通的放后面。如果用户有多个手机号,要做分类排列。
我想了个新方案:把每个号码单独处理,如果是手机号就判断运营商,然后重新排序。写着写着就冒出一个想法——能不能在原列表上直接替换一段,插入新的元素,同时去掉旧的?
于是我用上了切片赋值:
# 伪代码逻辑:把第i个元素替换成多个元素
row[i:i+1] = processed_numbers测试的时候一切正常,结果一跑全量数据,某些行的长度莫名其妙翻倍了,有的甚至变短了。我一个个打断点查,明明替换的是一个元素,怎么整个列表长度都变了?
那天下午我对着屏幕发呆了两个小时,后来才恍然大悟:切片赋值根本不是替换单个元素,它是把切片区间的内容整个换成新内容——新内容有几个元素,替换后列表就有多长。
在说切片赋值之前,先复习一下最基础的索引赋值。
arr = [1, 2, 3, 4, 5]
arr[2] = 100
print(arr) # [1, 2, 100, 4, 5]这个没什么好说的:把索引2的位置换成100,列表长度不变。
索引赋值是「一对一」的替换,新值占老值的位置,长度永远不变。
这就像你开了一家水果店,货架上第三个格子原来放着苹果,现在换成橙子,货架的总格子数没变。
接下来是切片赋值。记住这一句话:
切片赋值是用右侧的可迭代内容,替换掉左侧切片区间里的所有元素。
关键点在于:右侧有多少个元素,切片区间就被替换成多少个元素。多退少补,长度随之变化。
看几个直观的例子:
arr = [1, 2, 3, 4, 5]
arr[1:3] = [100, 200, 300, 400]
print(arr) # [1, 100, 200, 300, 400, 4, 5]原来的arr[1:3]是[2, 3],两个元素。我把它替换成了四个元素[100, 200, 300, 400],列表从5个变成了7个,变长了。
这相当于你把货架上两个格子里的苹果拿走,放回去四个橙子——货架变长了,多出来的空间塞进去了新水果。
arr = [1, 2, 3, 4, 5]
arr[1:4] = [100]
print(arr) # [1, 100, 5]原来的arr[1:4]是[2, 3, 4],三个元素被替换成了一个[100],列表从5个变成了3个,变短了。
arr = [1, 2, 3, 4, 5]
arr[1:3] = []
print(arr) # [1, 4, 5]右侧是空列表,相当于把[2, 3]这两个元素删掉了。这就是另一种删除列表元素的方式。
arr = [1, 2, 3, 4, 5]
arr[1:3] = [100, 200]
print(arr) # [1, 100, 200, 4, 5]替换前后的元素数量一致,长度不变,和索引赋值看起来效果差不多。
怎么理解切片赋值的机制?Python内部其实做了三件事:
所以切片赋值等价于:
# arr[1:4] = [100, 200]
# 等价于
del arr[1:4] # arr变成 [1, 5]
arr[1:1] = [100, 200] # 在索引1的位置插入100和200
# 结果 [1, 100, 200, 5]你只要记住「先删后插」这四个字,就能理解一切切片赋值的行为。删了几个,插了几个,长度就是「原来长度 - 删掉的个数 + 插入的个数」。
有个特别容易踩的坑:切片赋值右侧必须是一个可迭代对象,不能直接放一个值。
arr = [1, 2, 3, 4, 5]
arr[1:3] = 100 # ❌ TypeError: can only assign an iterable如果你不小心写成了这样,程序直接报错。正确写法是:
arr[1:3] = [100] # ✅ 包裹在列表里这点和索引赋值不一样:arr[1] = 100可以直接赋值,切片赋值必须给个可迭代对象。
不带步长的切片赋值已经够让人迷糊了,带步长的更离谱。
先看一段代码:
arr = [1, 2, 3, 4, 5, 6]
arr[1:5:2] = [100, 200]
print(arr) # [1, 100, 3, 200, 5, 6]步长为2,选取的是索引1和3这两个位置,分别替换成100和200。带步长时,替换的元素数量必须严格相等。
arr = [1, 2, 3, 4, 5, 6]
arr[1:5:2] = [100, 200, 300] # ❌ ValueError: attempt to assign sequence of size 3 to extended slice of size 2为什么带步长时不能改变长度?因为带步长的切片选中的位置是不连续的,无法通过「删除+插入」来改变长度——总不能把不连续的几个坑位塞成连续的一大块吧?Python干脆强制要求数量必须匹配。
记住两条截然不同的规则:
了解了一个东西的坑,也要知道它能做什么。切片赋值能变长变短的这个特性,其实有很多实用的用法。
替换中间几个元素为更多内容:
# 把周日到周二的3天,换成一周7天的安排
schedule = ["周一", "周二", "周三"]
schedule[0:3] = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
print(schedule) # 变长了arr = [1, 2, 5, 6]
arr[2:2] = [3, 4] # 在索引2的位置插入3和4,不删除任何元素
print(arr) # [1, 2, 3, 4, 5, 6]这比insert只能插一个要灵活得多。arr[2:2] = [3, 4]就是在索引2的位置插入两个元素。
arr = [1, 2, 3, 4, 5]
arr[1:4] = [] # 删除索引1到3的元素
print(arr) # [1, 5]arr = [1, 2, 3, 4, 5]
arr[:] = [] # 清空列表,但保留原列表对象
print(arr) # []arr = []会创建一个新列表,arr[:] = []是清空原列表,区别在于其他引用是否指向同一个对象。
回到我当初那个数据清洗脚本。我的代码写了类似这样的逻辑:
row[i:i+1] = processed_numbers当时processed_numbers有时候返回2个元素,有时候返回3个,有时候返回空列表。于是同一行数据,有的位置替换完变长了,有的变短了,后面的索引全部错位,处理结果一塌糊涂。
正确的做法其实很简单:要么新建一个列表来装处理后的结果,要么把切片赋值的逻辑单独拎出来,先计算好所有的替换结果,再统一赋值,而不是在遍历的同时修改列表长度。
如果非要在原列表上操作,记住一个技巧:从后往前遍历。当你从后往前修改时,前面的索引不会受到后面长度变化的影响。
arr = [1, 2, 3, 4, 5]
for i, val in enumerate(arr):
if val % 2 == 0:
arr[i:i+1] = [val, val*2] # 偶数翻倍插入
# 可能会无限循环或者跳过元素永远不要在遍历列表的同时修改列表长度。要修改就新建一个列表。
arr = [1, 2, 3]
arr[1] = [100, 200] # 索引赋值:arr变成 [1, [100, 200], 3]
arr[1:2] = [100, 200] # 切片赋值:arr变成 [1, 100, 200, 3]这两个长得像但效果天差地别,一个是嵌套,一个是展开。
arr = [1, 2, 3, 4, 5]
arr[::2] = [100, 200] # ❌ ValueError,长度不匹配写法 | 含义 | 长度变化 | 右侧要求 |
|---|---|---|---|
arr[i] = x | 索引赋值 | 不变 | 任意值 |
arr[i:j] = x | 切片赋值(无步长) | 可能变 | 必须是可迭代对象 |
arr[i:j:k] = x | 切片赋值(有步长) | 必须不变 | 必须和切片长度一致 |
arr[i:j] = [] | 删除区间 | 变短 | 空可迭代对象 |
arr[i:i] = x | 在i位置插入 | 变长 | 可迭代对象 |
最后送你三句话:
那次踩坑之后,我每次写切片赋值都会先停顿三秒:我到底是想改一个元素,还是想换一批元素?我会让右侧的元素数量和切片的长度心里有数,而不是随手一写。
希望你看了这篇,别再重蹈我的覆辙。切片赋值是个好东西,前提是你要懂它的脾性。懂了,它就是一把削铁如泥的刀;不懂,它就会在你不注意的时候把列表切得乱七八糟。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。