首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python的切片赋值把我坑惨了,原来改个值还能改变列表长度

Python的切片赋值把我坑惨了,原来改个值还能改变列表长度

原创
作者头像
风一样的男子
发布2026-08-25 13:58:32
发布2026-08-25 13:58:32
300
举报
文章被收录于专栏:编程教程编程教程

先给你讲个真事儿。

去年我写一个数据清洗脚本,处理一批用户提交的表格数据。每一行是一个用户的多个电话号码,格式不规范,有的有区号,有的没有,有的用斜杠分隔,有的用逗号。

我的思路很简单:先把每一行按分隔符拆成一个列表,然后统一格式化。

原始数据大概长这样:

代码语言:javascript
复制
raw_data = [
    ["010-88888888", "010-99999999"],   # 固定电话
    ["13800138000"],                     # 手机号
    ["010-66666666", "020-77777777", "13900139000"],  # 混搭
]

我要把每个列表里带区号的固话统一格式,手机号保留原样。这逻辑不复杂,我写了段代码循环处理:

代码语言:javascript
复制
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'],没问题。

但马上需求变了,领导说:手机号也要拆一下,按运营商分类,移动的放前面,联通的放后面。如果用户有多个手机号,要做分类排列。

我想了个新方案:把每个号码单独处理,如果是手机号就判断运营商,然后重新排序。写着写着就冒出一个想法——能不能在原列表上直接替换一段,插入新的元素,同时去掉旧的?

于是我用上了切片赋值:

代码语言:javascript
复制
# 伪代码逻辑:把第i个元素替换成多个元素
row[i:i+1] = processed_numbers

测试的时候一切正常,结果一跑全量数据,某些行的长度莫名其妙翻倍了,有的甚至变短了。我一个个打断点查,明明替换的是一个元素,怎么整个列表长度都变了?

那天下午我对着屏幕发呆了两个小时,后来才恍然大悟:切片赋值根本不是替换单个元素,它是把切片区间的内容整个换成新内容——新内容有几个元素,替换后列表就有多长。

先搞明白普通索引赋值

在说切片赋值之前,先复习一下最基础的索引赋值。

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
arr[2] = 100
print(arr)  # [1, 2, 100, 4, 5]

这个没什么好说的:把索引2的位置换成100,列表长度不变。

索引赋值是「一对一」的替换,新值占老值的位置,长度永远不变。

这就像你开了一家水果店,货架上第三个格子原来放着苹果,现在换成橙子,货架的总格子数没变。

切片赋值:核心就一句话

接下来是切片赋值。记住这一句话:

切片赋值是用右侧的可迭代内容,替换掉左侧切片区间里的所有元素。

关键点在于:右侧有多少个元素,切片区间就被替换成多少个元素。多退少补,长度随之变化。

看几个直观的例子:

替换为更多元素——变长

代码语言:javascript
复制
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个,变长了

这相当于你把货架上两个格子里的苹果拿走,放回去四个橙子——货架变长了,多出来的空间塞进去了新水果。

替换为更少元素——变短

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
arr[1:4] = [100]
print(arr)  # [1, 100, 5]

原来的arr[1:4][2, 3, 4],三个元素被替换成了一个[100],列表从5个变成了3个,变短了

替换为空列表——删除元素

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
arr[1:3] = []
print(arr)  # [1, 4, 5]

右侧是空列表,相当于把[2, 3]这两个元素删掉了。这就是另一种删除列表元素的方式。

替换为相同数量——长度不变

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
arr[1:3] = [100, 200]
print(arr)  # [1, 100, 200, 4, 5]

替换前后的元素数量一致,长度不变,和索引赋值看起来效果差不多。

本质:切片赋值是「删除+插入」的组合拳

怎么理解切片赋值的机制?Python内部其实做了三件事:

  1. 删除左侧切片区间里的所有元素
  2. 展开右侧的可迭代对象,取出所有元素
  3. 插入这些元素到删除位置

所以切片赋值等价于:

代码语言:javascript
复制
# arr[1:4] = [100, 200]
# 等价于
del arr[1:4]      # arr变成 [1, 5]
arr[1:1] = [100, 200]  # 在索引1的位置插入100和200
# 结果 [1, 100, 200, 5]

你只要记住「先删后插」这四个字,就能理解一切切片赋值的行为。删了几个,插了几个,长度就是「原来长度 - 删掉的个数 + 插入的个数」。

切片赋值的右侧必须是可迭代对象

有个特别容易踩的坑:切片赋值右侧必须是一个可迭代对象,不能直接放一个值。

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
arr[1:3] = 100  # ❌ TypeError: can only assign an iterable

如果你不小心写成了这样,程序直接报错。正确写法是:

代码语言:javascript
复制
arr[1:3] = [100]  # ✅ 包裹在列表里

这点和索引赋值不一样:arr[1] = 100可以直接赋值,切片赋值必须给个可迭代对象。

更迷惑的:带步长的切片赋值

不带步长的切片赋值已经够让人迷糊了,带步长的更离谱。

先看一段代码:

代码语言:javascript
复制
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。带步长时,替换的元素数量必须严格相等。

代码语言:javascript
复制
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干脆强制要求数量必须匹配。

记住两条截然不同的规则:

  • 不带步长的切片赋值:长度可以变,右侧元素数量任意
  • 带步长的切片赋值:长度必须一致,右侧元素数量必须和切片长度相同

这个特性有什么用?

了解了一个东西的坑,也要知道它能做什么。切片赋值能变长变短的这个特性,其实有很多实用的用法。

用法1:批量替换列表中的一段

替换中间几个元素为更多内容:

代码语言:javascript
复制
# 把周日到周二的3天,换成一周7天的安排
schedule = ["周一", "周二", "周三"]
schedule[0:3] = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
print(schedule)  # 变长了

用法2:在任意位置插入多个元素

代码语言:javascript
复制
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的位置插入两个元素。

用法3:删除任意区间的元素

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
arr[1:4] = []  # 删除索引1到3的元素
print(arr)  # [1, 5]

用法4:清空整个列表

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
arr[:] = []  # 清空列表,但保留原列表对象
print(arr)  # []

arr = []会创建一个新列表,arr[:] = []是清空原列表,区别在于其他引用是否指向同一个对象。

我踩的那个坑,现在回头看

回到我当初那个数据清洗脚本。我的代码写了类似这样的逻辑:

代码语言:javascript
复制
row[i:i+1] = processed_numbers

当时processed_numbers有时候返回2个元素,有时候返回3个,有时候返回空列表。于是同一行数据,有的位置替换完变长了,有的变短了,后面的索引全部错位,处理结果一塌糊涂。

正确的做法其实很简单:要么新建一个列表来装处理后的结果,要么把切片赋值的逻辑单独拎出来,先计算好所有的替换结果,再统一赋值,而不是在遍历的同时修改列表长度。

如果非要在原列表上操作,记住一个技巧:从后往前遍历。当你从后往前修改时,前面的索引不会受到后面长度变化的影响。

几个容易翻车的场景

场景1:同时修改和遍历

代码语言:javascript
复制
arr = [1, 2, 3, 4, 5]
for i, val in enumerate(arr):
    if val % 2 == 0:
        arr[i:i+1] = [val, val*2]  # 偶数翻倍插入
# 可能会无限循环或者跳过元素

永远不要在遍历列表的同时修改列表长度。要修改就新建一个列表。

场景2:误把切片赋值当索引赋值

代码语言:javascript
复制
arr = [1, 2, 3]
arr[1] = [100, 200]   # 索引赋值:arr变成 [1, [100, 200], 3]
arr[1:2] = [100, 200] # 切片赋值:arr变成 [1, 100, 200, 3]

这两个长得像但效果天差地别,一个是嵌套,一个是展开。

场景3:带步长时忘记数量匹配

代码语言:javascript
复制
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 删除。

目录
  • 先搞明白普通索引赋值
  • 切片赋值:核心就一句话
    • 替换为更多元素——变长
    • 替换为更少元素——变短
    • 替换为空列表——删除元素
    • 替换为相同数量——长度不变
  • 本质:切片赋值是「删除+插入」的组合拳
  • 切片赋值的右侧必须是可迭代对象
  • 更迷惑的:带步长的切片赋值
  • 这个特性有什么用?
    • 用法1:批量替换列表中的一段
    • 用法2:在任意位置插入多个元素
    • 用法3:删除任意区间的元素
    • 用法4:清空整个列表
  • 我踩的那个坑,现在回头看
  • 几个容易翻车的场景
    • 场景1:同时修改和遍历
    • 场景2:误把切片赋值当索引赋值
    • 场景3:带步长时忘记数量匹配
  • 总结一张速查表
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档