当前位置: 首页 > news >正文

Pythoncopy深拷贝与浅拷贝

Python copy 深拷贝与浅拷贝
===============================

copy 模块提供浅拷贝和深拷贝操作, 理解两者的区别对避免副作用至关重要。

1. 浅拷贝 vs 深拷贝: 核心概念
--------------------------------

import copy

# 浅拷贝: 创建新对象, 但只复制一层引用, 嵌套对象仍共享
# 深拷贝: 递归复制所有嵌套对象, 完全独立

# 基本类型 (int, str, float, bool) 是不可变的,
# 浅拷贝和深拷贝对它们没有实际区别
a = 42
b = copy.copy(a)
print("基本类型浅拷贝:", a is b) # True (小整数被缓存, 但不是所有情况)

# 主要区别体现在复合对象: 列表, 字典, 自定义对象
nested = [[1, 2], [3, 4]]
shallow = copy.copy(nested)
deep = copy.deepcopy(nested)

# 浅拷贝: 外层是新列表, 但内层列表是同一对象
print("浅拷贝外层独立:", nested is shallow) # False
print("浅拷贝内层共享:", nested[0] is shallow[0]) # True

# 深拷贝: 内外层都是独立对象
print("深拷贝外层独立:", nested is deep) # False
print("深拷贝内层独立:", nested[0] is deep[0]) # False

2. copy.copy vs copy.deepcopy — 实战演示
------------------------------------------

import copy

original = {
"name": "Alice",
"scores": [85, 92, 78],
"address": {"city": "北京", "zip": "100000"}
}

# 浅拷贝
shallow_copy = copy.copy(original)

# 深拷贝
deep_copy = copy.deepcopy(original)

# 修改浅拷贝中的嵌套对象
shallow_copy["scores"].append(99) # 同时影响原始对象!
shallow_copy["address"]["city"] = "上海" # 同时影响原始对象!
print("修改浅拷贝后:")
print(" 原始对象:", original) # scores 多了 99, city 变成了上海
print(" 浅拷贝:", shallow_copy)

# 修改深拷贝中的嵌套对象
deep_copy["scores"].append(100)
deep_copy["address"]["city"] = "广州"
print("\n修改深拷贝后:")
print(" 原始对象:", original) # 不受影响
print(" 深拷贝:", deep_copy) # 独立变化

# 浅拷贝顶层独立: 替换顶层键不影响原始
shallow_copy["name"] = "Bob" # 只影响浅拷贝
print("顶层替换不影响原始:", original["name"]) # 仍是 Alice

3. __copy__ 和 __deepcopy__ 自定义协议
------------------------------------------

import copy

class DatabaseConnection:
"""模拟数据库连接, 浅拷贝共享连接, 深拷贝创建新连接"""

def __init__(self, conn_str: str):
self.conn_str = conn_str
self.connected = False
self.transactions = [] # 事务列表, 应独立

def connect(self):
print(f"连接数据库: {self.conn_str}")
self.connected = True

def __copy__(self):
"""浅拷贝: 复用相同的连接字符串但不共享事务"""
new = type(self)(self.conn_str)
new.connected = self.connected # 共享连接状态
return new # transactions 是空列表

def __deepcopy__(self, memo):
"""深拷贝: 完全独立的事务列表"""
new = type(self)(self.conn_str)
new.connected = self.connected
new.transactions = copy.deepcopy(self.transactions, memo)
return new

def add_transaction(self, txn):
self.transactions.append(txn)

conn = DatabaseConnection("postgres://localhost/db")
conn.connect()
conn.add_transaction("TXN001")

# 浅拷贝: transactions 独立 (因为 __copy__ 返回空列表)
shallow_conn = copy.copy(conn)
shallow_conn.add_transaction("TXN002")
print("原始事务 (浅拷贝不影响):", conn.transactions) # ['TXN001']
print("浅拷贝事务:", shallow_conn.transactions) # ['TXN002']

# 深拷贝: 完全独立
deep_conn = copy.deepcopy(conn)
deep_conn.add_transaction("TXN003")
print("原始事务 (深拷贝不影响):", conn.transactions) # ['TXN001']

# 另一个例子: 单例模式对象应覆盖 __copy__ 和 __deepcopy__
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __copy__(self):
return self # 返回单例自身
def __deepcopy__(self, memo):
return self # 返回单例自身

s1 = Singleton()
s2 = copy.copy(s1)
s3 = copy.deepcopy(s1)
print("单例浅拷贝:", s1 is s2) # True
print("单例深拷贝:", s1 is s3) # True

4. 浅拷贝的常见方式
---------------------

# 方式1: copy.copy
import copy
list_a = [1, [2, 3]]
list_b = copy.copy(list_a)

# 方式2: 列表切片 ([:] 就是浅拷贝)
list_c = list_a[:]
print("切片浅拷贝:", list_a[1] is list_c[1]) # True

# 方式3: list() 构造函数
list_d = list(list_a)
print("list() 浅拷贝:", list_a[1] is list_d[1]) # True

# 方式4: dict.copy() 方法
dict_a = {"key": [1, 2]}
dict_b = dict_a.copy()
print("dict.copy() 浅拷贝:", dict_a["key"] is dict_b["key"]) # True

# 方式5: 列表的 copy() 方法 (Python 3.3+)
list_e = list_a.copy()
print("list.copy() 浅拷贝:", list_a[1] is list_e[1]) # True

# 方式6: 集合同样浅拷贝
set_a = {1, 2, 3}
set_b = set_a.copy()
print("set.copy() 浅拷贝:", set_a is set_b) # False

5. deepcopy 的 memo 字典机制
-------------------------------

import copy

# deepcopy 使用 memo 字典跟踪已复制的对象, 避免:
# 1. 重复拷贝同一对象
# 2. 对象间的循环引用导致无限递归

class Node:
def __init__(self, name):
self.name = name
self.children = []
self.parent = None

# 构建循环引用图
root = Node("root")
child = Node("child")
root.children.append(child)
child.parent = root # 循环引用: root -> child -> root

# deepcopy 能正确处理循环引用
copied_root = copy.deepcopy(root)
copied_child = copied_root.children[0]
print("深拷贝循环引用成功:", copied_child.parent.name) # root
print("循环引用结构保持一致:", copied_child.parent is copied_root) # True

# 理解 memo 字典的工作方式
original_list = [1, 2, 3]
memo = {}
# 手动模拟 deepcopy 的 memo 跟踪
# memo 的键是原始对象的 id, 值是已创建的副本
copied = copy.deepcopy(original_list, memo) # 内部使用 memo
print("Memo 跟踪:", id(original_list) in memo) # True

# 在 __deepcopy__ 中手动管理 memo
class ManagedNode:
def __init__(self, name):
self.name = name
self.ref = None

def __deepcopy__(self, memo):
# 先检查 memo 是否已有该对象的副本
if id(self) in memo:
return memo[id(self)]
new = ManagedNode(copy.deepcopy(self.name, memo))
memo[id(self)] = new
# ref 字段稍后由外部设置
return new

a = ManagedNode("A")
b = ManagedNode("B")
a.ref = b
b.ref = a # 双向循环引用

copied_a = copy.deepcopy(a)
print("ManagedNode 深拷贝:", copied_a.ref.ref.name) # A

6. 深拷贝的限制
------------------

import copy

# 1. 单例对象: deepcopy 默认返回单例自身
singleton = None # None 是单例
print("None 深拷贝:", copy.deepcopy(singleton) is singleton) # True

# 其他单例: True, False, Ellipsis, NotImplemented
print("True 深拷贝:", copy.deepcopy(True) is True) # True

# 2. 模块: 不可深拷贝
import math
try:
copy.deepcopy(math)
except TypeError as e:
print("模块不可深拷贝:", e)

# 3. 文件句柄等系统资源: 不可拷贝
try:
copy.deepcopy(open)
except TypeError as e:
print("打开文件不可拷贝:", e) # 但函数本身可拷贝引用, 实际打开的文件不可

# 4. 包含自定义 __slots__ 且没有 __dict__ 的对象
class SlotsOnly:
__slots__ = ("x",)
def __init__(self, x):
self.x = x

s = SlotsOnly(10)
try:
s_copy = copy.deepcopy(s)
print("__slots__ 对象深拷贝:", s_copy.x) # 可正常工作
except:
print("__slots__ 深拷贝失败")

# 但如果没有 __dict__ 且自定义 __getstate__/__setstate__ 可能有问题

# 5. 递归限制
import sys
print("默认递归深度:", sys.getrecursionlimit()) # 默认 1000
# 极端深嵌套可用递归深度限制保护:
class DeepNode:
def __init__(self, depth=0):
self.depth = depth
self.next = DeepNode(depth + 1) if depth < 500 else None
# 超过递归深度的 deepcopy 会 RecursionError

7. 性能对比
--------------

import copy
import time

# 构建一个大对象
data = {
"level1": [
{"name": f"item_{i}", "values": list(range(100))}
for i in range(100)
],
"metadata": {"created": "2024-01-01", "version": "1.0"},
}

# 浅拷贝性能
start = time.perf_counter()
for _ in range(1000):
s = copy.copy(data)
shallow_time = time.perf_counter() - start

# 深拷贝性能
start = time.perf_counter()
for _ in range(1000):
d = copy.deepcopy(data)
deep_time = time.perf_counter() - start

print(f"浅拷贝 1000 次: {shallow_time*1000:.1f} ms")
print(f"深拷贝 1000 次: {deep_time*1000:.1f} ms")
print(f"深拷贝/浅拷贝 速度比: {deep_time/shallow_time:.1f}x")

# 手动实现浅拷贝 vs 深拷贝的时间差异根源
# 浅拷贝: 只复制顶层结构 (字典本身的哈希表)
# 深拷贝: 递归访问每个嵌套对象, 对每个元素调用 copy

# 性能优化: 配合 __deepcopy__ 自定义加速
class OptimizedData:
def __init__(self, data):
self.data = data
self.cache = None

def __deepcopy__(self, memo):
# 只深拷贝重要的 data 字段, 跳过 cache
new = OptimizedData(copy.deepcopy(self.data, memo))
# cache 不拷贝, 由新对象按需生成
return new

print("\n深拷贝优化建议:")
print(" - 实现 __copy__/__deepcopy__ 跳过不需要复制的字段")
print(" - 小对象用浅拷贝即可, 避免不必要的深拷贝")
print(" - 对不可变嵌套结构考虑使用共享引用而非拷贝")

总结: 浅拷贝经济但共享嵌套对象; 深拷贝完整独立但成本高; 通过自定义协议可平衡二者.

http://www.jsqmd.com/news/906244/

相关文章:

  • 2026 年搭建 AI 智能体必看:Hermes Agent 的 6 个核心优势与实战教程
  • 【限时解密】Sora 2未公开API调试接口+本地化推理加速套件(仅开放前200名技术订阅者获取)
  • AI矩阵系统为什么成为企业线上获客的新趋势?
  • 告别盲目下断点:Keil5调试效率翻倍的5个高级技巧与避坑指南
  • 低成本Ambisonic麦克风DIY:用USB声卡实现空间音频录制
  • 为什么很多企业项目,越来越需要“快速响应”能力?
  • 【Sora 2短视频创作黄金法则】:20年AI内容专家亲授5大不可逆趋势与3步落地工作流
  • Sora 2 VR视频制作终极避坑清单(含12个已知bug编号、临时绕过方案及官方Patch ETA)
  • CMDB 系统:一次生产事故之后,所有人都开始重视它
  • 海曦技术:全栈算力筑基,软硬一体赋能产业智能升级
  • 零数学基础入门AI的补课路径:不从头啃高数,而是按认证需求补
  • 【Latex可变长不等号】用overset实现可变长不等号
  • 2026年最硬核的语言模型知识:从评估指标到Transformer架构,一篇全搞定!
  • 2026年移动端自动化测试平台选型指南:多终端测试全覆盖
  • 新电脑Ubuntu20编译老版本OpenWrt 15踩坑记:从GCC降级到13个报错修复全流程
  • 卖工程塑料怎么找客户?这几类工厂是核心目标
  • 有哪些能导入论文自动生成答辩PPT的工具?求真实使用推荐
  • 从零打造音乐律动LED圣诞树:micro:bit与Neopixel的创客实践
  • 工艺知识,是制造企业最昂贵的隐形资产——当老师傅退休,工艺优化靠什么传承?
  • C#控制台调用VISA踩坑实录:从‘找不到设备’到稳定通信,我都经历了什么?
  • 电力电子技术基础与DC-DC转换器原理
  • 为使用Claude Code的网站开发者,配置Taotoken稳定替代方案避免封号
  • 基于ESP32-C6与开普勒定律的微型太阳系模型:低功耗机电一体化实践
  • 北大提出把图结构视为 Agent 的长期记忆底座:SAGE 让大模型记忆自己进化!
  • 解决Claude Code访问不稳定问题,迁移至Taotoken的平稳过渡方案
  • 解码韬定律:从“τ缩微”到“衡×真×旋”
  • 保姆级教程:Vivado 2019.2 与 Modelsim 2019.2 联调避坑指南(从安装到编译一次成功)
  • 动态IP代理和静态IP代理的区别?新手也能看懂
  • MYSQL--函数,约束
  • 不止于安装HAP:用hdc_std命令行玩转OpenHarmony设备文件管理、日志抓取与性能调优