Day36 - 浅拷贝与深拷贝详解
详细讲解
1. 对象赋值与引用
在 Python 中,一切皆对象。赋值语句(如 a = b)并不复制对象,而是创建了对同一对象的引用。
# 赋值 - 同一个对象的不同引用
a = [1, 2, 3]
b = a
print(f"a: {a}") # [1, 2, 3]
print(f"b: {b}") # [1, 2, 3]
print(f"a is b: {a is b}") # True - 同一对象
# 修改 a,b 也会变化
a.append(4)
print(f"修改后 a: {a}") # [1, 2, 3, 4]
print(f"修改后 b: {b}") # [1, 2, 3, 4] - b 也变了!
# id() 显示对象身份
print(f"id(a): {id(a)}")
print(f"id(b): {id(b)}") # 相同
2. 浅拷贝(Shallow Copy)
浅拷贝创建一个新对象,但只复制原对象中的第一层引用。对于嵌套对象,只复制其引用,不复制嵌套对象本身。
import copy
# 浅拷贝的几种方式
original = [1, 2, [3, 4]]
# 方式1: copy.copy()
shallow1 = copy.copy(original)
# 方式2: list() / dict() / 切片等
shallow2 = list(original)
# 方式3: 切片
shallow3 = original[:]
# 方式4: dict(original)
original_dict = {'a': 1, 'b': [1, 2]}
shallow_dict = dict(original_dict)
print(f"original: {original}")
print(f"shallow: {shallow1}")
print(f"original is shallow: {original is shallow1}") # False - 新对象
print(f"original[2] is shallow[2]: {original[2] is shallow1[2]}") # True - 内部对象相同
# 修改第一层,不影响原对象
shallow1[0] = 999
print(f"修改后 original: {original}") # [1, 2, [3, 4]] - 不变
print(f"修改后 shallow: {shallow1}") # [999, 2, [3, 4]]
# 修改深层对象,影响原对象
shallow1[2].append(5)
print(f"修改深层后 original: {original}") # [1, 2, [3, 4, 5]] - 变了!
print(f"修改深层后 shallow: {shallow1}") # [999, 2, [3, 4, 5]]
3. 深拷贝(Deep Copy)
深拷贝递归地复制所有嵌套对象,创建完全独立的对象副本。
import copy
original = [1, 2, [3, 4]]
# 深拷贝
deep = copy.deepcopy(original)
print(f"original: {original}")
print(f"deep: {deep}")
print(f"original is deep: {original is deep}") # False
print(f"original[2] is deep[2]: {original[2] is deep[2]}") # False - 完全独立!
# 修改第一层,不影响原对象
deep[0] = 999
print(f"修改后 original: {original}") # [1, 2, [3, 4]] - 不变
print(f"修改后 deep: {deep}") # [999, 2, [3, 4]]
# 修改深层对象,也不影响原对象
deep[2].append(5)
print(f"修改深层后 original: {original}") # [1, 2, [3, 4]] - 不变!
print(f"修改深层后 deep: {deep}") # [999, 2, [3, 4, 5]]
4. 拷贝操作对比表
┌─────────────────────────────────────────────────────────────┐ │ 拷贝方式对比 │ ├─────────────┬───────────────────────────────────────────────┤ │ 赋值 (=) │ 共享引用,修改互相影响 │ ├─────────────┼───────────────────────────────────────────────┤ │ 浅拷贝 │ 第一层独立,深层共享引用 │ ├─────────────┼───────────────────────────────────────────────┤ │ 深拷贝 │ 完全独立,所有层级都复制 │ └─────────────┴───────────────────────────────────────────────┘5. 不可变对象与拷贝
重要:对于不可变对象(如 tuple, str, int, frozenset),由于其不可修改,拷贝的意义不大,Python 可能会复用对象。
import copy
# 不可变对象
a = "hello"
b = copy.copy(a)
c = copy.deepcopy(a)
print(f"a: {a}, b: {b}, c: {c}")
print(f"a is b: {a is b}") # True - 不可变对象可能复用
print(f"a is c: {a is c}") # True - 不可变对象可能复用
# tuple 整体不可变,但内部可变对象呢?
t = (1, 2, [3, 4])
t_copy = copy.copy(t)
t_deep = copy.deepcopy(t)
print(f"t is t_copy: {t is t_copy}") # True - tuple 不可变,copy 可能复用
print(f"t is t_deep: {t is t_deep}") # False - deepcopy 总是创建新对象
# 但内部的 list 是独立的
t_copy[2].append(5)
print(f"t: {t}") # (1, 2, [3, 4]) - tuple 不变
print(f"t_copy: {t_copy}") # (1, 2, [3, 4, 5])
6. 浅拷贝的常见方式
# 1. 切片拷贝
list1 = [1, 2, 3]
list2 = list1[:]
# 2. list() / dict() / set()
list3 = list(list1)
dict1 = {'a': 1}
dict2 = dict(dict1)
set1 = {1, 2}
set2 = set(set1)
# 3. list.copy() / dict.copy()
list4 = list1.copy()
dict3 = dict1.copy()
# 4. copy.copy()
import copy
list5 = copy.copy(list1)
# 5. copy.copy() for dict with nested
original = {'a': [1, 2, 3], 'b': {'x': 1}}
shallow = original.copy()
7. 深拷贝的常见方式
import copy
# 1. copy.deepcopy()
original = [1, 2, [3, 4]]
deep = copy.deepcopy(original)
# 2. 递归自定义深拷贝
def deep_copy(obj):
if isinstance(obj, (list, tuple)):
return type(obj)(deep_copy(item) for item in obj)
elif isinstance(obj, dict):
return {key: deep_copy(val) for key, val in obj.items()}
elif isinstance(obj, (int, str, float, bool, type(None))):
return obj
else:
# 其他对象尝试使用 __dict__ 复制
try:
result = object.__new__(type(obj))
result.__dict__.update(obj.__dict__)
return result
except:
return obj
8. 拷贝的注意事项
8.1 循环引用
import copy
# 循环引用的对象
list1 = [1, 2]
list2 = [3, 4]
list1.append(list2)
list2.append(list1) # list1 -> list2 -> list1
# deepcopy 能处理循环引用,不会无限递归
deep = copy.deepcopy(list1)
print(f"深拷贝完成,无无限递归")
8.2 对象自定义拷贝行为
import copy
class MyClass:
def __init__(self, value):
self.value = value
self.data = [1, 2, 3]
# 定义浅拷贝行为
def __copy__(self):
new_obj = MyClass(self.value)
new_obj.data = list(self.data) # 重新创建列表
return new_obj
# 定义深拷贝行为
def __deepcopy__(self, memo):
new_obj = MyClass(self.value)
memo[id(self)] = new_obj # 防止循环引用
new_obj.data = copy.deepcopy(self.data, memo)
return new_obj
obj = MyClass(42)
obj_copy = copy.copy(obj) # 调用 __copy__
obj_deep = copy.deepcopy(obj) # 调用 __deepcopy__
8.3 单例对象
import copy
# 单例对象(如 None, True, False)
singleton = None
copy_of_singleton = copy.copy(singleton)
deep_copy_of_singleton = copy.deepcopy(singleton)
print(f"singleton is copy: {singleton is copy_of_singleton}") # True
# 深拷贝也保持单例特性
print(f"singleton is deep: {singleton is deep_copy_of_singleton}") # True
9. 实战案例:配置对象拷贝
import copy
from dataclasses import dataclass, field
@dataclass
class DatabaseConfig:
"""数据库配置"""
host: str = 'localhost'
port: int = 3306
name: str = 'testdb'
credentials: dict = field(default_factory=dict)
@dataclass
class AppConfig:
"""应用配置"""
name: str = 'MyApp'
debug: bool = False
db: DatabaseConfig = field(default_factory=DatabaseConfig)
def create_copy(self):
"""创建深拷贝"""
return copy.deepcopy(self)
def create_shallow(self):
"""创建浅拷贝"""
return copy.copy(self)
# 创建原始配置
config1 = AppConfig(
name='Production',
debug=False,
db=DatabaseConfig(
host='db.example.com',
credentials={'user': 'admin'}
)
)
# 深拷贝 - 完全独立
config2 = config1.create_copy()
config2.name = 'Development'
config2.db.host = 'localhost' # 不影响 config1
print(f"原始配置: {config1.name}, {config1.db.host}")
print(f"拷贝配置: {config2.name}, {config2.db.host}")
# 输出:
# 原始配置: Production, db.example.com
# 拷贝配置: Development, localhost
# 浅拷贝 - 嵌套对象共享引用
config3 = config1.create_shallow()
config3.name = 'Testing'
config3.db.host = '127.0.0.1' # 这会影响到 config1!
print(f"原始配置: {config1.name}, {config1.db.host}")
print(f"浅拷贝配置: {config3.name}, {config3.db.host}")
# 输出:
# 原始配置: Production, 127.0.0.1 <- 被影响了!
# 浅拷贝配置: Testing, 127.0.0.1
10. 拷贝判断方法
import copy
a = [1, 2, 3]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print(f"a is b: {a is b}") # True - 同一引用
print(f"a is c: {a is c}") # False - 浅拷贝
print(f"a is d: {a is d}") # False - 深拷贝
# 检查值是否相等
print(f"a == b: {a == b}") # True
print(f"a == c: {a == c}") # True
print(f"a == d: {a == d}") # True
背诵版
三种拷贝方式速查
┌─────────────────────────────────────────────────────────────┐ │ 方式 │ 第一层 │ 深层(嵌套) │ 说明 │ ├───────────┼──────────┼────────────────┼────────────────────┤ │ 赋值 = │ 共享 │ 共享 │ 同一引用 │ ├───────────┼──────────┼────────────────┼────────────────────┤ │ 浅拷贝 │ 独立 │ 共享 │ copy.copy() │ ├───────────┼──────────┼────────────────┼────────────────────┤ │ 深拷贝 │ 独立 │ 独立 │ copy.deepcopy() │ └───────────┴──────────┴────────────────┴────────────────────┘拷贝方法总结
| 方法 | 类型 | 说明 |
|---|---|---|
a = b |
赋值 | 共享引用 |
a[:] |
浅拷贝 | 切片,适用于 list |
list(a) |
浅拷贝 | 构造函数 |
a.copy() |
浅拷贝 | 方法形式 |
copy.copy(a) |
浅拷贝 | 模块函数 |
copy.deepcopy(a) |
深拷贝 | 完全独立副本 |
考前记忆
面试重点
-
赋值 vs 拷贝的区别
- 赋值:创建新引用,指向同一对象
- 拷贝:创建新对象
-
浅拷贝只复制第一层
- 第一层是独立的
- 深层嵌套对象仍是引用
-
深拷贝完全独立
- 所有层级都递归复制
- 循环引用也能正确处理
-
不可变对象的特点
- int, str, tuple 等不需要拷贝
- 拷贝可能复用对象
-
__copy__和__deepcopy__方法- 自定义类可定义这两个方法控制拷贝行为
记忆口诀
赋值共享同一对象, 浅拷贝第一层独立。 深拷贝完全递归复制, immutable无需拷贝。测试题
选择题
1. 下面代码的输出是什么?
import copy
a = [1, [2, 3]]
b = copy.copy(a)
b[0] = 99
b[1].append(4)
print(a)
# A. [1, [2, 3]]
# B. [99, [2, 3]]
# C. [1, [2, 3, 4]]
# D. [99, [2, 3, 4]]
答案:C(第一层独立,深层共享)
2. 深拷贝的特点是什么?
# A. 只复制第一层
# B. 创建新引用
# C. 递归复制所有嵌套对象
# D. 不复制不可变对象
答案:C
3. list1 = list2[:] 是什么类型的拷贝?
# A. 赋值
# B. 浅拷贝
# C. 深拷贝
# D. 都不是
答案:B
4. 下面哪个是深拷贝的正确方式?
# A. a = b
# B. a = copy.copy(b)
# C. a = copy.deepcopy(b)
# D. a = b[:]
答案:C
5. 不可变对象(int, str, tuple)拷贝时会怎样?
# A. 总是创建新对象
# B. 可能复用已有对象
# C. 报错
# D. 返回 None
答案:B(不可变对象拷贝可能复用,Python 优化)
编程题
1. 实现一个安全的配置合并函数:
import copy
def merge_configs(base, override):
"""
合并配置,override 的值会覆盖 base
使用深拷贝确保不修改原始配置
"""
result = copy.deepcopy(base)
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = merge_configs(result[key], value)
else:
result[key] = copy.deepcopy(value)
return result
# 测试
base_config = {
'database': {
'host': 'localhost',
'port': 3306,
'options': {'timeout': 30}
},
'app': {'debug': False}
}
override_config = {
'database': {
'host': 'production.db.com',
'options': {'timeout': 60}
},
'app': {'debug': True}
}
merged = merge_configs(base_config, override_config)
print(merged)
# {'database': {'host': 'production.db.com', 'port': 3306, 'options': {'timeout': 60}},
# 'app': {'debug': True}}
# 验证原始配置未被修改
print(base_config['database']['host']) # 'localhost'
2. 实现一个不可变配置类:
import copy
from typing import Any
class ImmutableConfig:
"""不可变配置类"""
def __init__(self, data):
self._data = self._deep_freeze(data)
@staticmethod
def _deep_freeze(obj):
"""深度冻结 - 将所有嵌套 dict 转为 ImmutableConfig"""
if isinstance(obj, dict):
return ImmutableConfig(obj._data if isinstance(obj, ImmutableConfig) else obj)
elif isinstance(obj, list):
return tuple(ImmutableConfig._deep_freeze(item) for item in obj)
else:
return obj
def get(self, key, default=None):
"""获取值"""
keys = key.split('.')
value = self._data
for k in keys:
if isinstance(value, dict):
value = value.get(k)
elif isinstance(value, ImmutableConfig):
value = value._data.get(k)
else:
return default
if value is None:
return default
return value
def with_overrides(self, **kwargs):
"""返回带覆盖的新实例(原实例不变)"""
new_data = copy.deepcopy(self._data)
for key, value in kwargs.items():
new_data[key] = value
return ImmutableConfig(new_data)
def __repr__(self):
return f"ImmutableConfig({self._data})"
# 使用
config = ImmutableConfig({
'host': 'localhost',
'port': 8080,
'database': {
'name': 'testdb',
'user': 'admin'
}
})
# 获取值
print(config.get('host')) # localhost
print(config.get('database.name')) # testdb
# 创建新配置(不修改原配置)
new_config = config.with_overrides(host='production', port=443)
print(config.get('host')) # localhost - 原配置不变
print(new_config.get('host')) # production
3. 检测拷贝类型的装饰器:
import copy
def verify_copy(original, copied, expected_type):
"""验证拷贝结果的正确性"""
checks = {
'assignment': lambda o, c: o is c,
'shallow': lambda o, c: o is not c and o[2] is c[2], # 假设有嵌套
'deep': lambda o, c: o is not c and (len(o) != len(c) or o[2] is not c[2])
}
return checks.get(expected_type, lambda *a: False)(original, copied)
# 测试
nested = [1, [2, 3]]
assignment = nested
shallow = copy.copy(nested)
deep = copy.deepcopy(nested)
print(f"Assignment: {nested is assignment}") # True
print(f"Shallow: {nested is not shallow and nested[1] is shallow[1]}") # True
print(f"Deep: {nested is not deep and nested[1] is not deep[1]}") # True
问答题
Q1: 什么是浅拷贝?什么是深拷贝?请举例说明它们的区别。
- 浅拷贝:创建一个新对象,但只复制原对象的第一层。对于嵌套的可变对象(如 list 中的 list),只复制引用,不复制嵌套对象本身。
- 深拷贝:递归地复制所有嵌套对象,创建完全独立的对象副本。
import copy
original = [1, [2, 3]]
shallow = copy.copy(original)
shallow[1].append(4) # original 也会变化
deep = copy.deepcopy(original)
deep[1].append(5) # original 不受影响
Q2: 什么情况下需要使用深拷贝而不是浅拷贝?
需要深拷贝的场景:
- 修改副本时不想影响原对象
- 原对象包含多层嵌套的可变对象
- 需要对配置、状态等进行隔离修改
- 多线程或并发场景下需要独立状态
典型场景:
- 配置文件修改
- 游戏对象克隆
- 数据处理中的数据隔离
- 状态机中的状态副本
Q3: Python 中哪些对象是可变的?哪些是不可变的?
可变对象:
- list, dict, set
- bytearray
- 用户自定义类(默认)
不可变对象:
- int, float, bool
- str, bytes
- tuple, frozenset
- None
- complex, decimal
重要:包含可变对象的不可变对象(如包含 list 的 tuple)修改内部可变对象时,原对象也会变化。