Effective Python 条款 13:善用星号解包,告别下标切片拆分的坑
- [Bilibili 同步视频](#Bilibili 同步视频)
- 传统下标切片拆分:看着能用,但隐患重重💣
- ✨星号解包登场:优雅拆分序列
- [⚠️ 星号解包的几条硬性语法红线](#⚠️ 星号解包的几条硬性语法红线)
-
- [规则 1:不能只有星号变量,必须搭配至少一个普通变量](#规则 1:不能只有星号变量,必须搭配至少一个普通变量)
- [规则 2:同一层级解包,最多只能出现 1 个星号表达式](#规则 2:同一层级解包,最多只能出现 1 个星号表达式)
- [规则 3:星号捕获结果永远是列表,哪怕没有剩余元素](#规则 3:星号捕获结果永远是列表,哪怕没有剩余元素)
- [🚀 实战场景:直接对迭代器、生成器做星号解包](#🚀 实战场景:直接对迭代器、生成器做星号解包)
- 总结📝
原文出自 Effective Python 第 13 条:避过带下标的切片来捕获多个元素,不要用切片做序列拆分
在日常写 Python 的过程中,我们经常会遇到这样的场景:拿到一个有序序列,想要取出前面若干个元素,剩下的全部收纳到另一个容器中。很多初学者第一反应就是用下标 + 切片完成取值,但这种写法暗藏不少维护陷阱。而 Python 提供的带星号的解包(starred expression),正是为解决这类拆分场景而生,代码更简洁,可读性更高,还能减少低级下标错误。
Bilibili 同步视频
传统下标切片拆分:看着能用,但隐患重重💣
举个业务小例子:我们拿到一份车辆车龄列表,把车辆按照车龄从大到小降序排序,希望拿到车龄最老、第二老的两台车,剩下全部车辆归为其他集合。
如果不了解星号解包,很多同学会写出下面的代码:
python
car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]
car_ages_descending = sorted(car_ages, reverse=True)
# 通过硬编码下标提取数据
oldest = car_ages_descending[0]
second_oldest = car_ages_descending[1]
others = car_ages_descending[2:]
print(oldest, second_oldest, others)
# 输出:20 19 [15, 9, 8, 7, 6, 4, 1, 0]
运行结果完全正确,功能是实现了,但是这里埋了维护雷区。
试想后续业务发生变化,我现在需要取出前 3 台最老的车辆。那我不仅要新增一个变量写[2],还要把切片的起始下标从2改成3。一旦修改变量,忘记同步修改切片下标,就会产生隐蔽的 Bug。这类下标错位问题,编译器不会直接报错,只会悄悄输出错误业务数据,排查起来十分折磨人。
普通的解包操作也有局限,普通解包要求变量数量必须严格和序列长度一一对应。下面这段代码直接运行会抛出ValueError异常,因为左边只写 2 个变量,右边序列足足有 10 个元素,数量对不上:
python
car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]
car_ages_descending = sorted(car_ages, reverse=True)
oldest, second_oldest = car_ages_descending
# ValueError: too many values to unpack (expected 2)
✨星号解包登场:优雅拆分序列
*星号解包可以接收普通变量接纳不了的剩余全部元素,自动打包成列表。改写上面的示例:
python
car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]
car_ages_descending = sorted(car_ages, reverse=True)
oldest, second_oldest, *others = car_ages_descending
print(oldest, second_oldest, others)
# 输出:20 19 [15, 9, 8, 7, 6, 4, 1, 0]
代码清爽很多!我们不需要再手写任何数字下标。后续如果需要提取前 3 个元素,只需要增加一个普通变量即可,星号会自动接收剩下所有内容,完全不用手动调整切片数字,从根源避免下标错位失误。
星号不只能放在末尾!可以出现在赋值左侧任意位置
这是很多人容易忽略的知识点,*others 不一定非要写在等号最右边,头部、中间位置都可以放置,实现取头取尾,中间全部捕获的效果。
示例 1:取第一个最大,取最后一个最小,中间全部归 others
python
oldest, *others, youngest = car_ages_descending
print(oldest, youngest, others)
# 输出:20 0 [19, 15, 9, 8, 7, 6, 4, 1]
示例 2:把最后两个单独拿出来,前面全部收纳
python
*others, second_youngest, youngest = car_ages_descending
print(youngest, second_youngest, others)
# 输出:0 1 [20, 19, 15, 9, 8, 7, 6, 4]
⚠️ 星号解包的几条硬性语法红线
星号解包很好用,但有语法约束,踩中直接抛出语法错误,我们来避坑:
规则 1:不能只有星号变量,必须搭配至少一个普通变量
不能孤零零只写星号变量,Python 不知道如何处理:
python
# 错误写法
*others = car_ages_descending
# SyntaxError: Starred assignment target must be in a list or tuple
规则 2:同一层级解包,最多只能出现 1 个星号表达式
同一个赋值语句同一级,不能写两个*,逻辑存在歧义,编译器拒绝执行:
python
# 错误写法
first, *middle, *second_middle, last = [1, 2, 3, 4]
# SyntaxError: two starred expressions in assignment
补充:多层嵌套的数据结构,不同嵌套层级各自可以使用星号,只是可读性会下降,不推荐日常业务大量使用。
嵌套解包小演示(了解即可,不建议照搬业务):
python
car_inventory = {
'Downtown': ('Silver Shadow', 'pinto', 'DMC'),
'Airport': ('Skyline', 'Viper', 'Gremlin', 'Nova'),
}
((loc1, (best1, *rest1)),
(loc2, (best2, *rest2))) = car_inventory.items()
print(f"Best at {loc1} is {best1}, {len(rest1)} others")
print(f"Best at {loc2} is {best2}, {len(rest2)} others")
# Best at Downtown is Silver Shadow, 2 others
# Best at Airport is Skyline, 3 others
规则 3:星号捕获结果永远是列表,哪怕没有剩余元素
如果序列除去普通变量之后,已经没有多余元素留给星号,不会报错,会得到一个空列表:
python
short_list = [1, 2]
first, second, *rest = short_list
print(first, second, rest)
# 输出:1 2 []
🚀 实战场景:直接对迭代器、生成器做星号解包
星号解包一大高光场景:处理文件、CSV 这类迭代器 / 生成器数据。
比如读取 CSV,我们希望把表头第一行单独拿出来,剩下所有数据行统一保存。如果用切片写法,必须先把整个迭代器强制转为完整列表,才能用下标[0]取表头,[1:]拿数据。
传统切片写法:
python
def generate_csv():
# 模拟csv生成器,产出表头+200行业务数据
yield ('Date', 'Make', 'Model', 'Year', 'price')
for i in range(200):
yield (f"2026-{i%12+1}", f"Brand{i}", f"Car{i}", 2020+i, 10000+i*100)
all_csv_rows = list(generate_csv())
header = all_csv_rows[0]
rows = all_csv_rows[1:]
print('CSV Header: ', header)
print('Row count: ', len(rows))
使用星号解包,代码更加直观:
python
def generate_csv():
yield ('Date', 'Make', 'Model', 'Year', 'price')
for i in range(200):
yield (f"2026-{i%12+1}", f"Brand{i}", f"Car{i}", 2020+i, 10000+i*100)
it = generate_csv()
header, *rows = it
print('CSV Header: ', header)
print('Row count: ', len(rows))
📌重要性能提醒:
*rows会把迭代器剩余全部元素消费,并且组装成内存中的列表。如果迭代器的数据量极其庞大,数据全部塞进内存,有 OOM 内存耗尽崩溃风险!如果是超大数据流,不适合用星号一次性全部捕获,建议逐行迭代处理,不要全部加载进内存。
总结📝
-
拆分序列提取部分元素,优先选择带星号的解包表达式,尽量少硬编码下标 + 切片,减少人为下标错位 Bug。
-
*变量可以放在赋值左侧任意位置,捕获剩下全部元素,结果永远是列表,无剩余元素时得到空列表。 -
语法约束:同一层最多一个星号;必须搭配普通变量,不能单独使用星号变量。
-
星号解包对生成器、迭代器非常友好,但要注意内存风险:大数据量不要一次性
*全部装入内存。

核心一句话:能用星号解包优雅拆分,就别手撕数字下标。