一、一行给多个变量赋值
普通赋值是一个变量装一个值:
a = 1
b = 2
c = 3
序列解包可以一行就给好几个变量同时赋值,只要右边是一串东西(列表、元组、字符串都行),左边用逗号隔开写变量名:
a, b, c = 1, 2, 3
print(a, b, c)
输出:
1 2 3
等号右边其实是被悄悄包成了一个元组 (1, 2, 3),再按位置一个个分给左边的变量。所以这句和下面这句效果一样:
t = (1, 2, 3)
a, b, c = t
print(a, b, c)
a, b, c = ... 就知道「这一行在拆出三个值」,不用写三行赋值。二、基本解包(列表/元组/字符串)
只要右边是「一串东西」,左边就能一次拆开。列表、元组、字符串都能解包。
① 列表解包
numbers = [1, 2, 3]
a, b, c = numbers
print(f'a: {a}')
print(f'b: {b}')
print(f'c: {c}')
输出:
a: 1
b: 2
c: 3
② 元组解包
person = ('小明', 25, '工程师')
name, age, job = person
print(f'姓名: {name}')
print(f'年龄: {age}')
print(f'职业: {job}')
输出:
姓名: 小明
年龄: 25
职业: 工程师
③ 字符串解包
word = 'abc'
x, y, z = word
print(f'x: {x}')
print(f'y: {y}')
print(f'z: {z}')
输出:
x: a
y: b
z: c
| 右边类型 | 例子 | 说明 |
|---|---|---|
| 列表 | a, b, c = [1, 2, 3] | 按位置拆成三个变量 |
| 元组 | a, b = (10, 20) | 和列表完全一样 |
| 字符串 | x, y, z = 'abc' | 每个字符当作一个元素 |
三、个数必须对上(两个真实 ValueError 原文)
解包时,左边变量个数必须和右边元素个数一模一样,多一个少一个都会报错。
① 右边多了(变量太少)
a, b = [1, 2, 3]
ValueError: too many values to unpack (expected 2)
② 右边少了(变量太多)
a, b = [1]
ValueError: not enough values to unpack (expected 2, got 1)
第一条说「想要 2 个值,但你给了 3 个」;第二条说「想要 2 个值,你只给了 1 个」。报错信息把你写错的个数都告诉你了,照着改就行。
* 来收下多余的。len():len([1,2,3]) 是 3。变量个数和它对齐就不会错。课程思政|工匠精神与严谨求实 · 数目对不上,程序不答应
解包有条硬规矩:左边变量个数,必须和右边元素个数一模一样,多一个少一个都会报错。有意思的是,报错信息会直接告诉你「想要 2 个、你给了 3 个」,把差多少算得明明白白。对不上就是不对,程序从不将就。做统计也一样,账要对得上、数目要核得清,含糊过去的问题,迟早会回来找你。
四、星号解包(first, *rest、*head, last、a, *middle, b)
元素个数不确定时,用 * 把「剩下的一坨」打包给一个变量。* 拿到的一定是个列表。
① 只要第一个,其余都收下
scores = [80, 90, 75, 85, 95]
first, *rest = scores
print(f'First score: {first}')
print(f'Rest scores: {rest}')
输出:
First score: 80
Rest scores: [90, 75, 85, 95]
② 只要最后一个,前面都收下
data = ('start', 1, 2, 3, 'end')
start, *middle, end = data
print(f'Start: {start}')
print(f'Middle: {middle}')
print(f'End: {end}')
输出:
Start: start
Middle: [1, 2, 3]
End: end
③ 三个变量的完整写法
a, *middle, b:第一个给 a,最后一个给 b,中间所有给 middle。
nums = [1, 2, 3, 4, 5]
head, *body, tail = nums
print(head, body, tail)
输出:
1 [2, 3, 4] 5
| 写法 | 含义 | 星号变量得到 |
|---|---|---|
first, *rest = lst | 第一个单独拿,其余打包 | 列表 |
*rest, last = lst | 最后一个单独拿,前面打包 | 列表 |
a, *m, b = lst | 首尾单独拿,中间打包 | 列表 |
*。写两个会直接报语法错:a, *b, c, *d = [1, 2, 3, 4, 5]
SyntaxError: multiple starred expressions in assignment
* 永远给你一个列表,哪怕只剩一个元素也是 [x],不是单独的值。五、交换变量
很多语言交换两个变量要借第三个「临时变量」,Python 用解包一行就搞定:
x = 10
y = 20
x, y = y, x
print(f'x: {x}')
print(f'y: {y}')
输出:
x: 20
y: 10
这句的右边 y, x 先组成一个元组 (20, 10),再解包给左边的 x, y。等价于「把 y 的值给 x,把 x 的值给 y」。
x = y; y = x(两行)。那样第二行 y = x 时,x 早就被改掉了,最后两个变量会相等,交换失败。x, y = y, x。AI 助手小贴士|交换变量那行「魔法」值得追问到底。
打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:a, b = b, a 为什么不借助临时变量就能交换?右边到底先算了什么?再给 3 个星号解包的实用例子
六、函数返回多值就是解包
一个函数用 return 写多个值(用逗号隔开),返回的是一个元组。调用时用多个变量接住,就是解包:
def get_point():
return 3, 4
x, y = get_point()
print('坐标:', x, y)
输出:
坐标: 3 4
再看一个实用的:函数同时返回最大值和最小值:
def max_min(lst):
return max(lst), min(lst)
big, small = max_min([8, 3, 12, 5])
print('最大:', big, '最小:', small)
输出:
最大: 12 最小: 3
p = get_point() 后 p 是 (3, 4)。def、return)——函数本身是第 22 篇《函数》的主题。现在只需要记住现象:返回多个值 = 拿到一个元组 = 解包接住,至于函数怎么写,到时候再说。课程思政|诚信与职业操守 · 说返回一个值,其实是元组
函数用 return 写多个值,看着像返回了好几个,其实返回的是一个元组;只用一个变量接,拿到的就是整个元组。写函数的人把这层讲清楚,调用的人就不会猜错。接口不骗人,代码才靠得住。说清楚自己给的是什么、要的是什么,这是协作里最基本的诚实。
七、嵌套解包(for name, (age, job) in info)
当序列里还套着序列,解包也能一层层拆开。常用于「每条记录带好几个字段」的场景。
① 列表里套列表
students = [('小明', [80, 90]), ('小红', [75, 85])]
for name, [score1, score2] in students:
print(f'姓名: {name}')
print(f'第一次: {score1}')
print(f'第二次: {score2}')
输出:
姓名: 小明
第一次: 80
第二次: 90
姓名: 小红
第一次: 75
第二次: 85
② 元组里套元组
points = ((1, 2), (3, 4))
for (x, y) in points:
print(f'x: {x}, y: {y}')
输出:
x: 1, y: 2
x: 3, y: 4
③ 更复杂的嵌套
info = [('小明', (25, '工程师')), ('小红', (30, '医生'))]
for name, (age, job) in info:
print(f'姓名: {name}')
print(f'年龄: {age}')
print(f'职业: {job}')
输出:
姓名: 小明
年龄: 25
职业: 工程师
姓名: 小红
年龄: 30
职业: 医生
for name, (age, job) in ... 表示每条数据是一个「名字 + (年龄, 职业)」的结构。八、解包还能这么用(for k, v in d.items()、print(*lst)、[*a, *b])
解包不止赋值,还能在好几个地方「展开」数据。
① 遍历字典:同时拿键和值
d = {'x': 1, 'y': 2}
for k, v in d.items():
print(k, v)
输出:
x 1
y 2
② 用 * 把列表「打散」传给 print
lst = [1, 2, 3]
print(*lst)
输出:
1 2 3
*lst 把列表「拆开」成三个独立的参数传给 print,效果等同 print(1, 2, 3)。
③ 用 [*a, *b] 合并两个列表
a = [1, 2]
b = [3, 4]
merged = [*a, *b]
print(merged)
输出:
[1, 2, 3, 4]
④ 用 * 收集函数多余参数
def add_all(*nums):
total = 0
for n in nums:
total += n
return total
print(add_all(1, 2, 3, 4))
输出:
10
* 在解包里有两个意思:放在赋值时(first, *rest = ...)是「收集剩余」;放在调用时(print(*lst))是「打散展开」。看位置就懂。课程思政|科技自立自强 · 同一个星号,两种含义
星号 * 在解包里有两副面孔:写在赋值左边,是「把剩下的收成一个列表」;写在调用里,却是「把列表打散展开」。同一个符号两种用法,看位置就能分辨,这正是把语言的脾气摸透了。读懂设计者的用意,才谈得上用好它。等技术学到这一层,我们才有底气去改它、去造属于自己的工具。
九、十个基础案例
下面 10 个小案例都很短,建议每个都亲手敲一遍、跑一遍。
案例 1:一行记下三杯奶茶的价格
a, b, c = 12, 15, 9
print(a, b, c)
输出:
12 15 9
案例 2:拆出快递架上的「第几排第几列」
rack = (3, 7)
row, col = rack
print('第', row, '排 第', col, '列')
输出:
第 3 排 第 7 列
案例 3:取一周课表的首尾,中间打包
week = ['周一', '周二', '周三', '周四', '周五']
first, *middle, last = week
print('开头:', first, '中间:', middle, '结尾:', last)
输出:
开头: 周一 中间: ['周二', '周三', '周四'] 结尾: 周五
案例 4:交换两个人的奶茶口味
m, n = '少冰', '常温'
m, n = n, m
print(m, n)
输出:
常温 少冰
案例 5:函数一次返回歌名和歌手
def song_info():
return '晴天', '周杰伦'
name, singer = song_info()
print(name, singer)
输出:
晴天 周杰伦
案例 6:遍历歌单,拆开每首的两个数据
data = [('起风了', [88, 92]), ('晴天', [76, 85])]
for name, [p1, p2] in data:
print(name, '平均', (p1 + p2) / 2)
输出:
起风了 平均 90.0
晴天 平均 80.5
案例 7:遍历奶茶菜单的键值对
price = {'珍珠奶茶': 12, '芝士葡萄': 18}
for name, p in price.items():
print(name, '一杯', p, '元')
输出:
珍珠奶茶 一杯 12 元
芝士葡萄 一杯 18 元
案例 8:取前两件快递,剩下的打包
parcels = ['A1', 'B2', 'C3', 'D4', 'E5']
first, second, *others = parcels
print(first, second, others)
输出:
A1 B2 ['C3', 'D4', 'E5']
案例 9:打散列表,打印宿舍床位
beds = [7, 8, 9]
print('宿舍床位', *beds, '已分配')
输出:
宿舍床位 7 8 9 已分配
案例 10:合并两份歌单
a = ['晴天', '稻香']
b = ['起风了', '告白气球', '七里香']
c = [*a, *b]
print(c)
输出:
['晴天', '稻香', '起风了', '告白气球', '七里香']
AI 助手小贴士|解包的实用性靠场景题来体会。
打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:出 3 道解包应用题:一行拆出名单的第一个和剩下的人、for 循环里同时拿下标和值、合并两个字典。给提示,先不给完整答案
十、常见坑
坑 1:左右个数对不上
a, b = [1, 2, 3] # 想拿 3 个却只写 2 个变量
报 ValueError: too many values to unpack (expected 2)。要么加变量,要么用 * 收下多余的。
坑 2:一个赋值写两个星号
a, *b, *c = [1, 2, 3, 4] # 两个星号,语法不允许
报 SyntaxError: multiple starred expressions in assignment。一个赋值只能有一个 *。
坑 3:忘了右边的「一串东西」
a, b = 5 # 5 是单个数字,不是序列
报 TypeError: cannot unpack non-iterable int object。解包右边必须是可以一个一个取的序列(列表/元组/字符串等)。('int' object is not iterable 是 for x in 5 这类写法的报错,别混。)
坑 4:以为 * 拿到的是单个值
first, *rest = [1, 2, 3]
print(rest + [4]) # rest 是列表,能直接相加
这里 rest 是 [2, 3](列表),不是 2。所以能和别的列表相加;想要里面第一个值要写 rest[0]。
a = b = c = 0 是三个变量都指向同一个值;a, b, c = 0, 0, 0 才是分别赋三个值。别混了。print(右边序列) 看一眼它到底有几个元素、长什么样,再决定左边写几个变量或用 *。十一、报错手册
下面这些报错都是真跑出来的原文,照抄本机解释器。看到眼熟的,直接对照处理。
① 右边元素太多
a, b = [1, 2, 3]
ValueError: too many values to unpack (expected 2)
原因:左边 2 个变量,右边 3 个元素。加一个变量 c,或把第二个改成 *rest。
② 右边元素太少
a, b = [1]
ValueError: not enough values to unpack (expected 2, got 1)
原因:左边要 2 个,右边只有 1 个。检查序列是不是空的或被截断了。
③ 两个星号
a, *b, c, *d = [1, 2, 3, 4, 5]
SyntaxError: multiple starred expressions in assignment
原因:一个赋值只能有一个 *。拆成两次赋值,或只保留一个 *。
④ 右边不是序列
a, b = 5
TypeError: cannot unpack non-iterable int object
原因:5 是单个整数,不能解包。右边必须是列表、元组、字符串这类「可迭代」对象。
⑤ 解包一个空序列
a, b = []
ValueError: not enough values to unpack (expected 2, got 0)
原因:空序列一个元素都没有。先确认数据有没有被正确读进来。
⑥ 解包字典(默认只拿到键)
d = {'x': 1, 'y': 2}
a, b = d
print(a, b)
输出:
x y
这不是报错,但新手常以为能拿到值。直接解包字典只会拿到键;要拿键值对得写 for k, v in d.items():。
AI 助手小贴士|a, b = b, a 这行「魔法」值得让 AI 拆开讲——一行代码背后是完整的求值顺序。
打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:a, b = b, a 为什么不借助临时变量就能交换两个数?右边到底先算了什么?再给我 3 个序列解包的实用例子
十二、随堂自测(10 题)
点击选项即可判分,选错的题会立刻展开解析。做完可以看到正确率和错题清单,也可以重新答题。
1执行 a, b, c = 1, 2, 3 后,三个变量分别得到什么?
- A.
a=1, b=2, c=3 - B.
a=1, b=1, c=1 - C.
a=3, b=2, c=1 - D. 报错,不能一次给三个变量赋值
(1,2,3),再按位置解包给左边;这是最基础的「序列解包」,完全合法。2a, b = [1, 2, 3] 会怎样?
- A. 正常,a=1, b=2
- B. 正常,a=1, b=[2,3]
- C. 报 ValueError(个数对不上)
- D. 报 TypeError
ValueError: too many values to unpack。想收下多余元素要用 a, *rest = ...。3first, *rest = [80, 90, 75, 85, 95] 中,rest 是什么类型?
- A. 一个单独的数字
- B. 一个列表
- C. 一个元组
- D. 一个字符串
* 的变量永远拿到一个列表,这里是 [90, 75, 85, 95],即使只剩一个元素也是列表。4想交换两个变量 x 和 y 的值,最简洁的写法是?
- A.
x = y; y = x - B.
temp = x; x = y; y = temp - C.
x, y = y, x - D.
swap(x, y)
x, y = y, x 利用解包一行完成交换;Python 没有内置 swap 函数,两行写法会丢失原值。5函数 def f(): return 3, 4,调用 a, b = f() 时发生了什么?
- A. 返回的就是元组,被解包成 a=3, b=4
- B. 只能用一个变量接,否则报错
- C. a 和 b 都变成 (3,4)
- D. 报 SyntaxError
(3,4),用多个变量接住就是解包。若只写 p = f(),p 才是整个元组。6for k, v in d.items(): 遍历字典时,每次循环 k 和 v 分别是什么?
- A. k 是值,v 是键
- B. k 是键,v 是值
- C. k 和 v 都是键
- D. k 和 v 都是值
d.items() 每次给出一个 (键, 值) 元组,解包后 k 拿到键、v 拿到值。这是遍历字典键值对的标准写法。7下面哪个赋值会报语法错?
- A.
a, b = [1, 2] - B.
a, *b = [1, 2] - C.
a, *b, c = [1, 2, 3] - D.
a, *b, *c = [1, 2, 3, 4]
*,写两个会报 SyntaxError: multiple starred expressions。其余三种都合法。8x, y, z = 'abc' 执行后,y 的值是?
- A.
'abc' - B.
97(字符 b 的编码) - C.
'b' - D. 报错,字符串不能解包
x='a'、y='b'、z='c'。9直接写 a, b = {'x': 1, 'y': 2}(不解包 items),a 和 b 拿到的是什么?
- A. 两个值 1 和 2
- B. 两个键 'x' 和 'y'
- C. 整个字典
- D. 报错
for k, v in d.items():。10print(*[1, 2, 3]) 和 print([1, 2, 3]) 的输出区别是?
- A. 完全一样
- B. 前者报错
- C. 后者报错
- D. 前者打出
1 2 3,后者打出[1, 2, 3]
*lst 在调用时把列表「打散」成独立参数,所以 print(*[1,2,3]) 输出 1 2 3;不加星号则把整个列表当一个参数,输出带方括号。