文章目录Python 可变对象与不可变对象下深浅拷贝的底层真相——为什么你的 copy 不好使导入语1 ~ 先回忆赋值 不是拷贝2 ~ 浅拷贝——只拷贝第一层2.1 浅拷贝长什么样2.2 那为什么说它浅2.3 内存图解释2.4 什么时候浅拷贝就够了3 ~ 深拷贝——递归拷贝每一层3.1 深拷贝长什么样3.2 内存图解释3.3 深拷贝如何应对循环引用4 ~ 一个真实的生产事故5 ~ copy vs deepcopy 速查表6 ~ 自定义类的深拷贝思考 总结结尾Python 可变对象与不可变对象下深浅拷贝的底层真相——为什么你的 copy 不好使最新推荐文章于 2026-06-16 12:00:00 发布 | 阅读 1.0k 阅读 | 分类Python基础入门文章简介上篇讲了赋值陷阱和函数传参本篇深入到深浅拷贝。copy()和deepcopy()到底差在哪为什么嵌套列表用copy()改内层元素还是会影响原对象本文用内存模型图逐层拆解浅拷贝的只拷贝第一层机制、深拷贝的递归复制原理、以及循环引用时deepcopy如何通过 memo 字典避免死循环。穿插真实踩坑经历一个配置表因为浅拷贝导致原始模板被污染的生产事故。读完你不仅知道用哪个还能说出内存层原因。 个人主页源码骑士❄专栏传送门《Android开发基础》《python基础课程》⭐️热衷从源码视角拆解技术底层原理将复杂架构讲得通俗易懂 源码骑士的简介5年Android Framework系统开发经验曾主导多项系统级性能优化专项技术栈覆盖Android系统全链路Binder/Handler/AMS/WMS/启动流程及Java后端全家桶Spring MyBatis Redis Oracle累计产出原创技术文章100篇文章以源码拆解为特色被读者评价为看一篇胜过啃一周文档导入语上篇讲到了可变对象和不可变对象的赋值陷阱——你知道了b a只是多贴一张标签。所以当你想真正复制一份独立的数据时自然会想到copy()。但现实远没这么简单。我 2021 年做配置中心项目时有一个深层嵌套的 YAML 配置模板开发同事用.copy()复制了一份去修改结果原始模板被污染了线上三个服务同时挂掉。回溯了三个小时才发现——嵌套结构用copy()等于没拷。问题的根源在于浅拷贝只拷贝第一层。深拷贝才递归拷贝每一层。这句话说着简单但画成内存图你会发现完全不一样。这篇文章把这两个的区别用图讲清楚。1 ~ 先回忆赋值不是拷贝a[1,2,3]ba# 赋值——b 和 a 指向同一个对象b.append(4)print(a)# [1, 2, 3, 4] ← 不是拷贝是共享赋值只是贴标签没有创建新对象。要真正创建一份独立的数据你需要拷贝。2 ~ 浅拷贝——只拷贝第一层2.1 浅拷贝长什么样importcopy a[1,2,3]ba.copy()# 方式一list.copy()ba[:]# 方式二切片——也等于浅拷贝blist(a)# 方式三构造器bcopy.copy(a)# 方式四copy 模块# 验证b 是新创建的独立对象print(aisb)# False ← 确实不是同一个对象了2.2 那为什么说它浅a[[1,2],[3,4]]# 外层列表装了两个内层列表ba.copy()# 修改外层——b 不受影响a.append([5,6])print(b)# [[1, 2], [3, 4]] ← b 外层没变符合预期# 但修改内层呢a[0].append(999)print(b[0])# [1, 2, 999] ← b 的内层变了2.3 内存图解释浅拷贝前 a →[指针1 , 指针2]↓ ↓[1,2][3,4]浅拷贝后 a →[指针1 , 指针2]↓ ↓[1,2][3,4]↑ ↑ b →[指针1 , 指针2]← 外层容器是新对象 ← 但内部指针指向的还是原来的内层对象浅拷贝创建了一个新的外层容器但外层容器里的引用指针指向的还是原来的内层对象。这就是只拷贝第一层的含义。2.4 什么时候浅拷贝就够了如果你的列表是一维的没有嵌套结构浅拷贝完全够用a[1,2,3]ba.copy()b[0]100print(a)# [1, 2, 3] ← a 没变浅拷贝成功一维列表中所有元素都是不可变的int/str 等不可能被原地修改所以改不了 b 同时影响 a。3 ~ 深拷贝——递归拷贝每一层3.1 深拷贝长什么样importcopy a[[1,2],[3,4]]bcopy.deepcopy(a)a[0].append(999)print(a[0])# [1, 2, 999]print(b[0])# [1, 2] ← b 不受影响内层也独立了3.2 内存图解释深拷贝后 a →[指针1 , 指针2]↓ ↓[1,2][3,4]← a 的内层对象 b →[指针3 , 指针4]↓ ↓[1,2][3,4]← b 的内层对象——全新的深拷贝递归地把每一层都复制了外层和内层全是独立的新对象。a 和 b 在内存里没有任何重叠。3.3 深拷贝如何应对循环引用a[1,2]a.append(a)# a 引用自己 → 循环引用bcopy.deepcopy(a)# 能跑不会死循环print(b[2]isb)# Trueb 也引用自己保持了一致的拓扑结构deepcopy内部维护了一个memo字典记录已拷贝过的对象 → 它的拷贝。遇到循环引用时memo 里已经有了直接复用——避免死递归。这是deepcopy设计的精妙之处也是为什么你要用copy.deepcopy()而不是自己写递归。4 ~ 一个真实的生产事故2021 年的配置中心项目。简化后代码大概长这样# 配置模板——深层嵌套DEFAULT_CONFIG{server:{host:0.0.0.0,port:8000,features:[auth,logging]# 注意这个列表}}# 每个客户有这个函数复制模板后改配置defget_customer_config(customer_name):configDEFAULT_CONFIG.copy()# 浅拷贝config[server][features].append(fcustom_{customer_name})returnconfig# 第1个客户config_aget_customer_config(A)# 第2个客户config_bget_customer_config(B)# 第1个客户的配置再次读取config_a2get_customer_config(A)print(config_a2[server][features])# [auth, logging, custom_A, custom_B, custom_A] ← 彻底废了根因分析.copy()是浅拷贝——DEFAULT_CONFIG 的顶层 dict 复制了一份但嵌套的 dict 和 list 没有复制。config[server][features]修改的仍然是 DEFAULT_CONFIG 内部的同一个 list。每次调用都在污染全局模板各客户之间的配置串了。修复把.copy()换成copy.deepcopy()。教训这是个代价不算小的教训。从那以后团队 Code Review 看到.copy()操作嵌套结构一律要求加注释说明为什么这里浅拷贝够用否则改成deepcopy。5 ~ copy vs deepcopy 速查表操作外层容器内层对象适合什么场景b a赋值共享共享不需要拷贝贴标签就行a.copy()浅复制✅ 新对象❌ 共享一维列表、只读内层copy.deepcopy(a)深复制✅ 新对象✅ 全新对象嵌套结构、不想有任何共享切片a[:]✅ 新对象❌ 共享同浅拷贝6 ~ 自定义类的深拷贝importcopyclassPerson:def__init__(self,name,hobbies):self.namename self.hobbieshobbies# hobbies 是 listp1Person(张三,[篮球,编程])p2copy.浅拷贝(p1)# Person 新对象但 .hobbies 共享p3copy.deepcopy(p1)# Person 新对象.hobbies 也是新列表p1.hobbies.append(吃饭)print(p2.hobbies)# [篮球, 编程, 吃饭] ← 浅拷贝的 hobbies 被污染print(p3.hobbies)# [篮球, 编程] ← 深拷贝独立类中如果包含可变属性的嵌套同样需要用deepcopy。有些情况可以覆写__copy__和__deepcopy__来定制拷贝行为但初学阶段知道有这个方法就行。思考 总结深浅拷贝的核心逻辑就三句话浅拷贝创建新的外层容器但内层元素仍然指向原对象。一维数据够用嵌套数据有共享风险。深拷贝递归复制每一层产生完全独立的对象图。memo字典防止循环引用导致死递归。判断是否需要深拷贝的方法很简单看你的数据结构有没有嵌套可变对象列表套列表、字典套列表、类属性是列表等。有嵌套 →deepcopy。只有一层 →copy()或a[:]就行。结尾各位小伙伴本文的内容到这里就全部结束了源码骑士在这里再次感谢您的阅读源码骑士 — Python 全栈 系统架构关注跟博主一起从源码视角深耕底层原理见证每一次成长❤️点赞让优质内容被更多人看见让知识传递更有力量⭐收藏把核心知识点存好在需要时随时查、随时用评论分享你的经验或疑问评论区一起交流避坑一键四连不要忘记给博主一键四连哦今日源码拆解达成️寄语技术之路难免有困惑但同行的人会让前进更有方向结语深浅拷贝的问题不在于语法难在于你对内存里对象是怎么连起来的有没有一张图。下次 Code Review 看到.copy()出现在嵌套结构上多看一眼——可能是个未来的线上事故。不要忘记给博主一键四连哦
03-Python可变对象与不可变对象(下)-深浅拷贝的底层真相
发布时间:2026/6/14 6:20:28
文章目录Python 可变对象与不可变对象下深浅拷贝的底层真相——为什么你的 copy 不好使导入语1 ~ 先回忆赋值 不是拷贝2 ~ 浅拷贝——只拷贝第一层2.1 浅拷贝长什么样2.2 那为什么说它浅2.3 内存图解释2.4 什么时候浅拷贝就够了3 ~ 深拷贝——递归拷贝每一层3.1 深拷贝长什么样3.2 内存图解释3.3 深拷贝如何应对循环引用4 ~ 一个真实的生产事故5 ~ copy vs deepcopy 速查表6 ~ 自定义类的深拷贝思考 总结结尾Python 可变对象与不可变对象下深浅拷贝的底层真相——为什么你的 copy 不好使最新推荐文章于 2026-06-16 12:00:00 发布 | 阅读 1.0k 阅读 | 分类Python基础入门文章简介上篇讲了赋值陷阱和函数传参本篇深入到深浅拷贝。copy()和deepcopy()到底差在哪为什么嵌套列表用copy()改内层元素还是会影响原对象本文用内存模型图逐层拆解浅拷贝的只拷贝第一层机制、深拷贝的递归复制原理、以及循环引用时deepcopy如何通过 memo 字典避免死循环。穿插真实踩坑经历一个配置表因为浅拷贝导致原始模板被污染的生产事故。读完你不仅知道用哪个还能说出内存层原因。 个人主页源码骑士❄专栏传送门《Android开发基础》《python基础课程》⭐️热衷从源码视角拆解技术底层原理将复杂架构讲得通俗易懂 源码骑士的简介5年Android Framework系统开发经验曾主导多项系统级性能优化专项技术栈覆盖Android系统全链路Binder/Handler/AMS/WMS/启动流程及Java后端全家桶Spring MyBatis Redis Oracle累计产出原创技术文章100篇文章以源码拆解为特色被读者评价为看一篇胜过啃一周文档导入语上篇讲到了可变对象和不可变对象的赋值陷阱——你知道了b a只是多贴一张标签。所以当你想真正复制一份独立的数据时自然会想到copy()。但现实远没这么简单。我 2021 年做配置中心项目时有一个深层嵌套的 YAML 配置模板开发同事用.copy()复制了一份去修改结果原始模板被污染了线上三个服务同时挂掉。回溯了三个小时才发现——嵌套结构用copy()等于没拷。问题的根源在于浅拷贝只拷贝第一层。深拷贝才递归拷贝每一层。这句话说着简单但画成内存图你会发现完全不一样。这篇文章把这两个的区别用图讲清楚。1 ~ 先回忆赋值不是拷贝a[1,2,3]ba# 赋值——b 和 a 指向同一个对象b.append(4)print(a)# [1, 2, 3, 4] ← 不是拷贝是共享赋值只是贴标签没有创建新对象。要真正创建一份独立的数据你需要拷贝。2 ~ 浅拷贝——只拷贝第一层2.1 浅拷贝长什么样importcopy a[1,2,3]ba.copy()# 方式一list.copy()ba[:]# 方式二切片——也等于浅拷贝blist(a)# 方式三构造器bcopy.copy(a)# 方式四copy 模块# 验证b 是新创建的独立对象print(aisb)# False ← 确实不是同一个对象了2.2 那为什么说它浅a[[1,2],[3,4]]# 外层列表装了两个内层列表ba.copy()# 修改外层——b 不受影响a.append([5,6])print(b)# [[1, 2], [3, 4]] ← b 外层没变符合预期# 但修改内层呢a[0].append(999)print(b[0])# [1, 2, 999] ← b 的内层变了2.3 内存图解释浅拷贝前 a →[指针1 , 指针2]↓ ↓[1,2][3,4]浅拷贝后 a →[指针1 , 指针2]↓ ↓[1,2][3,4]↑ ↑ b →[指针1 , 指针2]← 外层容器是新对象 ← 但内部指针指向的还是原来的内层对象浅拷贝创建了一个新的外层容器但外层容器里的引用指针指向的还是原来的内层对象。这就是只拷贝第一层的含义。2.4 什么时候浅拷贝就够了如果你的列表是一维的没有嵌套结构浅拷贝完全够用a[1,2,3]ba.copy()b[0]100print(a)# [1, 2, 3] ← a 没变浅拷贝成功一维列表中所有元素都是不可变的int/str 等不可能被原地修改所以改不了 b 同时影响 a。3 ~ 深拷贝——递归拷贝每一层3.1 深拷贝长什么样importcopy a[[1,2],[3,4]]bcopy.deepcopy(a)a[0].append(999)print(a[0])# [1, 2, 999]print(b[0])# [1, 2] ← b 不受影响内层也独立了3.2 内存图解释深拷贝后 a →[指针1 , 指针2]↓ ↓[1,2][3,4]← a 的内层对象 b →[指针3 , 指针4]↓ ↓[1,2][3,4]← b 的内层对象——全新的深拷贝递归地把每一层都复制了外层和内层全是独立的新对象。a 和 b 在内存里没有任何重叠。3.3 深拷贝如何应对循环引用a[1,2]a.append(a)# a 引用自己 → 循环引用bcopy.deepcopy(a)# 能跑不会死循环print(b[2]isb)# Trueb 也引用自己保持了一致的拓扑结构deepcopy内部维护了一个memo字典记录已拷贝过的对象 → 它的拷贝。遇到循环引用时memo 里已经有了直接复用——避免死递归。这是deepcopy设计的精妙之处也是为什么你要用copy.deepcopy()而不是自己写递归。4 ~ 一个真实的生产事故2021 年的配置中心项目。简化后代码大概长这样# 配置模板——深层嵌套DEFAULT_CONFIG{server:{host:0.0.0.0,port:8000,features:[auth,logging]# 注意这个列表}}# 每个客户有这个函数复制模板后改配置defget_customer_config(customer_name):configDEFAULT_CONFIG.copy()# 浅拷贝config[server][features].append(fcustom_{customer_name})returnconfig# 第1个客户config_aget_customer_config(A)# 第2个客户config_bget_customer_config(B)# 第1个客户的配置再次读取config_a2get_customer_config(A)print(config_a2[server][features])# [auth, logging, custom_A, custom_B, custom_A] ← 彻底废了根因分析.copy()是浅拷贝——DEFAULT_CONFIG 的顶层 dict 复制了一份但嵌套的 dict 和 list 没有复制。config[server][features]修改的仍然是 DEFAULT_CONFIG 内部的同一个 list。每次调用都在污染全局模板各客户之间的配置串了。修复把.copy()换成copy.deepcopy()。教训这是个代价不算小的教训。从那以后团队 Code Review 看到.copy()操作嵌套结构一律要求加注释说明为什么这里浅拷贝够用否则改成deepcopy。5 ~ copy vs deepcopy 速查表操作外层容器内层对象适合什么场景b a赋值共享共享不需要拷贝贴标签就行a.copy()浅复制✅ 新对象❌ 共享一维列表、只读内层copy.deepcopy(a)深复制✅ 新对象✅ 全新对象嵌套结构、不想有任何共享切片a[:]✅ 新对象❌ 共享同浅拷贝6 ~ 自定义类的深拷贝importcopyclassPerson:def__init__(self,name,hobbies):self.namename self.hobbieshobbies# hobbies 是 listp1Person(张三,[篮球,编程])p2copy.浅拷贝(p1)# Person 新对象但 .hobbies 共享p3copy.deepcopy(p1)# Person 新对象.hobbies 也是新列表p1.hobbies.append(吃饭)print(p2.hobbies)# [篮球, 编程, 吃饭] ← 浅拷贝的 hobbies 被污染print(p3.hobbies)# [篮球, 编程] ← 深拷贝独立类中如果包含可变属性的嵌套同样需要用deepcopy。有些情况可以覆写__copy__和__deepcopy__来定制拷贝行为但初学阶段知道有这个方法就行。思考 总结深浅拷贝的核心逻辑就三句话浅拷贝创建新的外层容器但内层元素仍然指向原对象。一维数据够用嵌套数据有共享风险。深拷贝递归复制每一层产生完全独立的对象图。memo字典防止循环引用导致死递归。判断是否需要深拷贝的方法很简单看你的数据结构有没有嵌套可变对象列表套列表、字典套列表、类属性是列表等。有嵌套 →deepcopy。只有一层 →copy()或a[:]就行。结尾各位小伙伴本文的内容到这里就全部结束了源码骑士在这里再次感谢您的阅读源码骑士 — Python 全栈 系统架构关注跟博主一起从源码视角深耕底层原理见证每一次成长❤️点赞让优质内容被更多人看见让知识传递更有力量⭐收藏把核心知识点存好在需要时随时查、随时用评论分享你的经验或疑问评论区一起交流避坑一键四连不要忘记给博主一键四连哦今日源码拆解达成️寄语技术之路难免有困惑但同行的人会让前进更有方向结语深浅拷贝的问题不在于语法难在于你对内存里对象是怎么连起来的有没有一张图。下次 Code Review 看到.copy()出现在嵌套结构上多看一眼——可能是个未来的线上事故。不要忘记给博主一键四连哦