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 都指着它,当然一起变。## 二、浅拷贝:只复制最外层想拿到独立副本,最直接的是浅拷贝:pythonc = a.copy() # 等价于 a[:] 和 list(a)代码:
```pythoninner = [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,内层还是同一个```ona = [1, 2实测一组数据(CPython 3.13):2 万行嵌套列表占 9.3MB,整体浅拷贝之后内存只增加 **0.16MB**——增加的正是 2 万个指针槽(20,000 × 8 字节 ≈ 156KB),所有内层对象原封不动地共享。
浅拷贝的真正含义:**外层新,内层旧。**## 三、深拷贝:递归到底的代价要连内层一起复制,就得用 `copy.deepcopy`,它会沿着引用一路递归下去,把整棵对象树复制一遍。代价是惊人的:```pythonlst = [{'k': [i]*10} for i in range(2000)], 3]b = a%timeit lst.copy() # 约 0.010ms
%timeit copy.deepcopy(lst) # 约 17.2ms```同样 2000 个元素,深拷贝比浅拷贝慢了大约 **1700 倍**。原因很直白:浅拷贝是 memcpy 级别的指针搬运,深拷贝要对每个嵌套对象做递归遍历、逐个新建、维护"已复制"备忘录(`memo` 字典,用来正确处理循环引用和共享子对象)。顺带说一个高频面试点:deepcopy 之所以能处理 `x.append(x)` 这种自引用结构,靠的就是 memo——复制前先查"这个对象是不是已经复制过了"。 ## 四、函数传参也一样:传的是引用
```pythondef fill(lst): lst.append(1)data = []fill(data)print(data) # [1] —— 出了函数还在```Python 的参数传递常被叫做"传对象引用":形参和实参是同一个对象的两个名字。想避免函数污染外部数据,入口处先 `data = data.copy()`。## 五、一个判断口诀最后给一个实用口诀,按需选拷贝深度:- **元素是不可变类型**(数字、字符串、元组):浅拷贝就够,反正它们改不了; # 只是赋值,没有复制b.a- **一层嵌套、只改外层**:浅拷贝;
– **嵌套可变对象、各层都要独立**:深拷贝,但先掂量那 1700 倍的性能差;- **只是想传个名字**:直接赋值,零成本。记住核心机制就够了:**Python 里 "=" 永远只复制指针。** 复制对象的任何承诺,都来自 `copy` 模块,而不是赋值运算符本身。(实测环境:CPython 3.13,Windows;代码可直接粘贴复现)ppend(4)print(a) # [1, 2, 3, 4] —— a 居然也变了````b = a` 一行都没"改错",`a` 却被牵连了。要解释这件事,得回到上一期讲过的那个事实:**Python 的变量不是盒子,是标签。**## 一、赋值的真相:复制的是指针,不是对象
网硕互联帮助中心



评论前必须登录!
注册