Python 的赋值为什么不会复制对象?拆开引用、浅拷贝与深拷贝

先看一段让新手崩溃的代码:

python 复制代码
a = [1, 2, 3]
b = a          # 只是赋值,没有复制
b.append(4)
print(a)       # [1, 2, 3, 4]  ------ a 居然也变了

b = a 一行都没"改错",a 却被牵连了。要解释这件事,得回到上一期讲过的那个事实:Python 的变量不是盒子,是标签。

一、赋值的真相:复制的是指针,不是对象

CPython 里每个变量名都只是指向某个 PyObject* 的引用。b = a 做的事只有一件:把 a 指向的那个指针抄给 b,再调用一次 Py_INCREF 把引用计数加一。对象本身一个字节都没动。

在解释器里验证:

python 复制代码
>>> id(a) == id(b)
True
>>> sys.getrefcount(a)
3    # a、b,加上传参临时持有的一次

id() 相同,说明 a 和 b 是同一个对象的两个名字。所谓"赋值",本质上就是把 8 字节的指针抄了一遍------这也解释了为什么 Python 里赋值快得不像话。

所以开头那段代码不是 bug:b.append(4) 操作的是那个唯一的列表对象,a 和 b 都指着它,当然一起变。

二、浅拷贝:只复制最外层

想拿到独立副本,最直接的是浅拷贝:

python 复制代码
c = a.copy()   # 等价于 a[:] 和 list(a)

浅拷贝会新建一个列表对象,把外层的指针数组逐个抄一遍------注意抄的还是指针。于是:

python 复制代码
inner = [9]
outer = [inner]
sh = outer.copy()
sh[0].append(8)
print(outer[0])          # [9, 8] ------ 内层被共享了
print(outer is sh)       # False,外层确实是新对象
print(outer[0] is sh[0]) # True,内层还是同一个

实测一组数据(CPython 3.13):2 万行嵌套列表占 9.3MB,整体浅拷贝之后内存只增加 0.16MB------增加的正是 2 万个指针槽(20,000 × 8 字节 ≈ 156KB),所有内层对象原封不动地共享。

浅拷贝的真正含义:外层新,内层旧。

三、深拷贝:递归到底的代价

要连内层一起复制,就得用 copy.deepcopy,它会沿着引用一路递归下去,把整棵对象树复制一遍。代价是惊人的:

python 复制代码
lst = [{'k': [i]*10} for i in range(2000)]
%timeit lst.copy()          # 约 0.010ms
%timeit copy.deepcopy(lst)  # 约 17.2ms

同样 2000 个元素,深拷贝比浅拷贝慢了大约 1700 倍 。原因很直白:浅拷贝是 memcpy 级别的指针搬运,深拷贝要对每个嵌套对象做递归遍历、逐个新建、维护"已复制"备忘录(memo 字典,用来正确处理循环引用和共享子对象)。

顺带说一个高频面试点:deepcopy 之所以能处理 x.append(x) 这种自引用结构,靠的就是 memo------复制前先查"这个对象是不是已经复制过了"。

四、函数传参也一样:传的是引用

python 复制代码
def fill(lst):
    lst.append(1)

data = []
fill(data)
print(data)   # [1] ------ 出了函数还在

Python 的参数传递常被叫做"传对象引用":形参和实参是同一个对象的两个名字。想避免函数污染外部数据,入口处先 data = data.copy()。

五、一个判断口诀

最后给一个实用口诀,按需选拷贝深度:

  • 元素是不可变类型(数字、字符串、元组):浅拷贝就够,反正它们改不了;
  • 一层嵌套、只改外层:浅拷贝;
  • 嵌套可变对象、各层都要独立:深拷贝,但先掂量那 1700 倍的性能差;
  • 只是想传个名字:直接赋值,零成本。

记住核心机制就够了:Python 里 "=" 永远只复制指针。 复制对象的任何承诺,都来自 copy 模块,而不是赋值运算符本身。

(实测环境:CPython 3.13,Windows;代码可直接粘贴复现)

相关推荐
SimonKing1 小时前
告别纸质合同,使用DocuSeal搭建私有电子签平台
java·后端·程序员
ljt27249606611 小时前
Python笔记(十三)--异常
笔记·python
日日大王学编程2 小时前
开源Agent项目记忆设计决策对比
python·ai·langchain·开源·ai编程
vipxieliang2 小时前
Python 魔法方法:让自定义对象更 Pythonic
python
雨晨源码(同名B站)2 小时前
深度学习毕业设计选题推荐:基于YOLO11的交通标志检测系统设计与实现(Python+Django+Vue3+对接deepseek)
人工智能·python·深度学习·机器学习·django·课程设计
starzy19902 小时前
Scala String操作:从插值器到正则的完整指南
开发语言·后端·scala
2601_967979122 小时前
python中str函数怎么用?使用方法与注意事项详解
python·使用方法·字符串转换·注意事项·str函数
青少儿编程课堂2 小时前
蓝桥杯青少组图形化+信息素养+电子学会Scratch考级:校园口算闯关解析
c++·python·算法·bfs·信息学竞赛
starzy19902 小时前
Scala Array操作:从定长数组到ArrayBuffer的完整指南
开发语言·后端·scala