Python 序列解包与深浅拷贝
目录
学习目标
- 掌握序列解包的各种技巧
- 理解可变对象与不可变对象的区别
- 掌握深拷贝与浅拷贝的区别和使用场景
- 理解 Python 的内存管理机制
一、序列解包
序列解包(Unpacking)是将可迭代对象中的元素分配给多个变量的语法。
1.1 基本解包
# 元组解包
coordinates = (10, 20)
x, y = coordinates
print(x, y) # 10 20
# 列表解包
names = ["Alice", "Bob", "Charlie"]
a, b, c = names
print(a, b, c) # Alice Bob Charlie
# 字符串解包
a, b, c = "Py3"
print(a, b, c) # P y 31.2 扩展解包(Python 3+)
# 使用 * 捕获剩余元素
numbers = [1, 2, 3, 4, 5]
first, *rest = numbers
print(first) # 1
print(rest) # [2, 3, 4, 5]
# 中间捕获
first, *middle, last = numbers
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# 只取首尾
first, *_, last = numbers
print(first, last) # 1 5
# 多个 *
# a, *b, *c = numbers # SyntaxError: 只能有一个 *1.3 解包的应用场景
# 交换变量
a, b = 10, 20
a, b = b, a
print(a, b) # 20 10
# 遍历字典
user = {"name": "Alice", "age": 25, "city": "Beijing"}
for key, value in user.items():
print(f"{key}: {value}")
# 遍历 enumerate
for index, (name, score) in enumerate([("Alice", 85), ("Bob", 92)]):
print(f"{index}: {name} - {score}")
# 函数返回多个值
def get_stats(data):
return min(data), max(data), sum(data) / len(data)
minimum, maximum, average = get_stats([1, 2, 3, 4, 5])
print(minimum, maximum, average) # 1 5 3.0
# 忽略不需要的值
data = (1, 2, 3, 4, 5)
a, _, c, _, e = data
print(a, c, e) # 1 3 51.4 解包函数参数
def greet(name, age, city):
print(f"{name}, {age}岁, 来自{city}")
info = ["Alice", 25, "Beijing"]
greet(*info) # Alice, 25岁, 来自Beijing
kwargs = {"name": "Bob", "age": 30, "city": "Shanghai"}
greet(**kwargs) # Bob, 30岁, 来自Shanghai
# 混合使用
greet("Charlie", **{"age": 35, "city": "Shenzhen"})二、可变对象与不可变对象
2.1 基本概念
| 类型 | 可变对象 | 不可变对象 |
|---|---|---|
| 数字 | int, float, complex | |
| 序列 | list | str, tuple |
| 映射 | dict | |
| 集合 | set | frozenset |
| 其他 | bool, NoneType |
2.2 可变对象的行为
# 列表是可变的
a = [1, 2, 3]
b = a # b 引用同一个对象
b.append(4)
print(a) # [1, 2, 3, 4] a 也变了!
print(b) # [1, 2, 3, 4]
print(a is b) # True2.3 不可变对象的行为
# 整数是不可变的
x = 10
y = x
y = 20
print(x) # 10 x 不变
print(y) # 20
print(x is y) # False
# 字符串是不可变的
s1 = "hello"
s2 = s1
s2 = s2 + " world"
print(s1) # hello s1 不变
print(s2) # hello world2.4 函数参数传递
# 不可变对象:函数内修改不影响外部
def modify_int(x):
x = 100
print(f"函数内: {x}")
a = 10
modify_int(a)
print(f"函数外: {a}") # 10(不变)
# 可变对象:函数内修改会影响外部
def modify_list(lst):
lst.append(4)
print(f"函数内: {lst}")
b = [1, 2, 3]
modify_list(b)
print(f"函数外: {b}") # [1, 2, 3, 4](变了!)
# 避免修改:传递副本
def safe_modify(lst):
lst = lst.copy() # 创建副本
lst.append(4)
print(f"函数内: {lst}")
c = [1, 2, 3]
safe_modify(c)
print(f"函数外: {c}") # [1, 2, 3](不变)三、id() 与内存
# id() 返回对象的内存地址
a = [1, 2, 3]
b = a
print(id(a)) # 如: 140234567890
print(id(b)) # 相同的地址
print(a is b) # True
# 创建新对象
c = [1, 2, 3]
print(id(c)) # 不同的地址
print(a is c) # False
print(a == c) # True(值相等)
# 小整数缓存
x = 100
y = 100
print(x is y) # True(-5 到 256 之间会缓存)
m = 1000
n = 1000
print(m is n) # False(大整数不缓存)四、浅拷贝与深拷贝
4.1 赋值 vs 浅拷贝
import copy
# 赋值(引用)
a = [1, 2, [3, 4]]
b = a
print(a is b) # True(同一对象)
# 浅拷贝(三种方式)
c = a.copy() # 方法1
d = list(a) # 方法2
e = a[:] # 方法3
f = copy.copy(a) # 方法4
print(a is c) # False(不同对象)
print(a == c) # True(值相等)
# 但浅拷贝只拷贝一层
a[2].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(c) # [1, 2, [3, 4, 5]] 嵌套列表也变了!
print(a[2] is c[2]) # True(嵌套对象仍共享)4.2 深拷贝
import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
print(a is b) # False
print(a[2] is b[2]) # False(嵌套对象也独立了!)
a[2].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(b) # [1, 2, [3, 4]] b 不受影响!4.3 浅拷贝 vs 深拷贝对比
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 外层对象 | 新建 | 新建 |
| 嵌套可变对象 | 共享引用 | 递归新建 |
| 性能 | 快 | 慢 |
| 内存 | 节省 | 占用多 |
| 适用场景 | 一层结构 | 多层嵌套结构 |
4.4 图解
原始对象 a = [1, 2, [3, 4]]
赋值 b = a:
a ──→ [1, 2, [3, 4]]
↑
b ─────────┘
浅拷贝 c = a.copy():
a ──→ [1, 2, ─┐
↓
c ──→ [1, 2, ─┘→ [3, 4]]
深拷贝 d = copy.deepcopy(a):
a ──→ [1, 2, ─→ [3, 4]]
d ──→ [1, 2, ─→ [3, 4]] (完全独立)五、拷贝的陷阱
5.1 浅拷贝的意外
# 矩阵初始化陷阱
matrix = [[0] * 3] * 3
print(matrix)
# [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
matrix[0][0] = 1
print(matrix)
# [[1, 0, 0], [1, 0, 0], [1, 0, 0]] 所有行都变了!
# 原因:三行引用同一个内部列表
print(matrix[0] is matrix[1]) # True
# 正确做法
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix)
# [[1, 0, 0], [0, 0, 0], [0, 0, 0]]5.2 深拷贝的循环引用
a = [1, 2]
a.append(a) # 循环引用:a 包含自己
print(a) # [1, 2, [...]]
# 深拷贝可以处理循环引用
import copy
b = copy.deepcopy(a)
print(b) # [1, 2, [...]]
print(b is b[2]) # True(保持循环引用结构)六、综合示例
"""
数据备份工具:理解深浅拷贝的实际应用
"""
import copy
# 模拟一个复杂的数据结构
original_data = {
"users": [
{"name": "Alice", "settings": {"theme": "dark", "notifications": True}},
{"name": "Bob", "settings": {"theme": "light", "notifications": False}},
],
"config": {"version": "1.0", "debug": False}
}
# 1. 赋值(危险!)
reference = original_data
reference["config"]["debug"] = True
print("赋值后 original:", original_data["config"]["debug"]) # True(原数据被修改)
# 恢复
original_data["config"]["debug"] = False
# 2. 浅拷贝(部分安全)
shallow = copy.copy(original_data)
shallow["config"] = {"version": "2.0"} # 修改外层对象,不影响原数据
print("浅拷贝修改外层后:", original_data["config"]["version"]) # 1.0(安全)
shallow["users"][0]["settings"]["theme"] = "blue" # 修改嵌套对象
print("浅拷贝修改嵌套后:", original_data["users"][0]["settings"]["theme"]) # blue(危险!)
# 恢复
original_data["users"][0]["settings"]["theme"] = "dark"
# 3. 深拷贝(完全安全)
deep = copy.deepcopy(original_data)
deep["users"][0]["settings"]["theme"] = "red"
print("深拷贝修改后:", original_data["users"][0]["settings"]["theme"]) # dark(安全!)小结
- 序列解包使用
*捕获剩余元素,可灵活分配变量 - 可变对象(list、dict、set)赋值是引用传递,修改会影响原对象
- 不可变对象(str、tuple、int)修改会创建新对象
- 浅拷贝只复制一层,嵌套的可变对象仍共享引用
- 深拷贝递归复制所有层级,完全独立但性能开销更大
- 使用
copy.copy()浅拷贝,copy.deepcopy()深拷贝 - 初始化嵌套列表时要避免
[[]] * n的陷阱
练习
- 有一个列表
data = [1, 2, [3, 4], {"a": 5}],分别使用赋值、浅拷贝、深拷贝创建副本,然后修改嵌套列表和字典,观察原数据的变化。 - 编写一个函数
safe_append(lst, item),在不影响原列表的情况下返回新列表(新列表末尾添加了 item)。 - 解释以下代码的输出:
a = [[]] * 3 a[0].append(1) print(a) - 使用序列解包,交换字典中的键值对:将
{"a": 1, "b": 2}变成{1: "a", 2: "b"}。 - 创建一个 3x3 的二维列表并初始化为 0,确保每个子列表是独立的对象。