# Python切片和序列解包怎么用?
面试官问:“Python切片和序列解包分别是什么?有哪些常见用法和易错点?”
很多人能写出nums[1:3]和a, b = [1, 2],但对负索引、步长、切片拷贝行为、星号解包和嵌套解包容易答不清楚。这道题要讲清楚的是切片和解包各自的语法边界、典型场景以及对象修改的影响。
# 简要回答
切片(slice)用于从序列中提取子序列,语法是seq[start:stop:step],返回新序列。支持负索引(从末尾倒数)、省略边界、反向步长等灵活写法。切片返回的是浅拷贝,修改子序列不影响原序列,但嵌套的可变对象仍然共享。
序列解包(unpacking)用于把序列元素一次性赋给多个变量,最常见的是a, b = [1, 2]。Python 3支持星号表达式*rest收集剩余元素,也支持嵌套解包。解包要求左右两侧元素数量匹配(除非用了星号),否则抛出ValueError。
两者都适用于列表、元组、字符串等序列类型。解包实际上只要求右侧是可迭代对象,所以集合、字典、生成器也能解包。
# 详细回答
# 切片基础语法
切片用三个参数指定范围:[start:stop:step]。
start:起始位置(包含),默认0。stop:结束位置(不包含),默认序列末尾。step:步长,默认1。
nums = [0, 1, 2, 3, 4, 5]
print(nums[1:4]) # [1, 2, 3]
print(nums[:3]) # [0, 1, 2] 从头开始
print(nums[3:]) # [3, 4, 5] 到末尾
print(nums[::2]) # [0, 2, 4] 每隔一个
print(nums[::-1]) # [5, 4, 3, 2, 1, 0] 反转
2
3
4
5
6
负索引从末尾倒数,-1表示最后一个元素:
print(nums[-3:]) # [3, 4, 5] 最后三个
print(nums[:-2]) # [0, 1, 2, 3] 去掉最后两个
print(nums[-4:-1]) # [2, 3, 4]
2
3

图中展示了正索引从0开始向右递增,负索引从-1(最后一个元素)开始向左递减,两者可以定位到同一个元素。
# 切片是浅拷贝
切片返回新对象,修改切片结果不影响原列表:
original = [1, 2, 3, 4]
sub = original[1:3]
sub[0] = 99
print(original) # [1, 2, 3, 4] 不变
print(sub) # [99, 3]
2
3
4
5
但如果列表内嵌套可变对象,拷贝的是引用:
matrix = [[1, 2], [3, 4]]
sub = matrix[0:1]
sub[0][0] = 99
print(matrix) # [[99, 2], [3, 4]] 原列表也变了
2
3
4
切片只复制了外层列表,里面的[1, 2]仍然是同一个对象。可变对象被原地修改,所有指向它的地方都能看到变化,原理见《Python中的可变对象和不可变对象有什么区别?》。
# 切片赋值
切片赋值会原地修改列表,长度可以和原切片不一样:
nums = [0, 1, 2, 3, 4]
nums[1:3] = [10, 20, 30] # 可以不等长
print(nums) # [0, 10, 20, 30, 3, 4]
nums[1:4] = [] # 删除元素
print(nums) # [0, 3, 4]
2
3
4
5
6
给切片赋空列表等价于del nums[1:4]。元组和字符串不可变,不支持切片赋值,列表和元组的这类差异见《Python列表和元组有什么区别?》。
# 序列解包基础
最简单的解包是一一对应:
point = (3, 4)
x, y = point
print(x, y) # 3 4
name, age = ["Alice", 25]
print(name, age) # Alice 25
2
3
4
5
6
左右元素数量必须匹配:
try:
a, b = [1, 2, 3]
except ValueError as e:
print(e) # too many values to unpack (expected 2)
2
3
4
# 星号表达式收集剩余元素
Python 3支持用*变量收集多余元素:
first, *rest = [1, 2, 3, 4]
print(first) # 1
print(rest) # [2, 3, 4]
*head, last = [1, 2, 3, 4]
print(head) # [1, 2, 3]
print(last) # 4
a, *middle, b = [1, 2, 3, 4, 5]
print(middle) # [2, 3, 4]
2
3
4
5
6
7
8
9
10
星号变量收集的始终是列表,即使只有一个元素或为空:
a, *b = [1]
print(b) # []
*x, y, z = [1, 2]
print(x) # []
2
3
4
5

图中展示了普通解包要求数量严格匹配,星号表达式可以收集剩余元素到一个列表中,位置灵活。
# 嵌套解包
解包可以嵌套,常用于多维数据:
point = (1, (2, 3))
x, (y, z) = point
print(x, y, z) # 1 2 3
users = [("Alice", 25), ("Bob", 30)]
for name, age in users:
print(name, age)
2
3
4
5
6
7
结构必须匹配,否则报错:
try:
a, (b, c) = [1, [2]]
except ValueError as e:
print(e) # not enough values to unpack (expected 2, got 1)
2
3
4
# 字符串也支持切片和解包
字符串是不可变序列,切片返回新字符串,背后原因见《Python字符串为什么不可变?常用操作有哪些?》:
s = "Python"
print(s[1:4]) # yth
print(s[::-1]) # nohtyP
first, *rest = s
print(first) # P
print(rest) # ['y', 't', 'h', 'o', 'n']
2
3
4
5
6
7
注意解包字符串时,每个字符作为一个元素。
# 知识拓展
面试官可能追问:
Q1:切片索引越界会报错吗?
不会。切片索引超出范围时,Python自动调整到有效范围:
nums = [1, 2, 3]
print(nums[10:20]) # []
print(nums[-10:2]) # [1, 2]
2
3
这和直接访问nums[10]会抛出IndexError不同。
Q2:如何复制整个列表?
nums[:]和nums.copy()都是浅拷贝:
original = [1, 2, 3]
copy1 = original[:]
copy2 = original.copy()
2
3
两者效果相同。需要深拷贝嵌套结构时,用copy.deepcopy()。
Q3:可以一次解包多个序列吗?
可以用zip()配合解包:
names = ["Alice", "Bob"]
ages = [25, 30]
for name, age in zip(names, ages):
print(name, age)
2
3
4
Q4:解包时可以忽略某些元素吗?
约定用_接收不需要的元素:
_, y, _ = [1, 2, 3]
print(y) # 2
first, *_ = [1, 2, 3, 4] # 只要第一个
2
3
4
_是普通变量名,只是约定俗成表示“忽略”。
Q5:字典可以解包吗?
字典解包时,默认迭代的是键:
d = {"a": 1, "b": 2}
x, y = d
print(x, y) # a b
# 要解包键值对,用 .items()
for k, v in d.items():
print(k, v)
2
3
4
5
6
7
函数调用时用**把字典展开成关键字参数,属于函数参数的话题,和这里的赋值解包不是一回事。
评论
验证登录状态...