在 Python 编程中,明明只修改了一个变量,另一个变量的值却跟着变了?或者试图复制一个列表,结果改了新列表,旧列表也被修改了?
这一切的根源,都在于 Python 的可变/不可变类型以及内存管理机制。今天我们就来彻底搞懂这些底层逻辑。
01 可变与不可变:内存的“房客”
Python 中的数据类型分为两大派系,区分的核心在于:当变量值改变时,内存地址是否随之改变。
不可变数据类型 (Immutable)
特点: 一旦创建,内存里的值就不能被修改。如果你想改变它的值,Python 会在内存中重新开辟一块空间存新值,并将变量指向新地址。
a = 6 # 堆内存存了6,a指向它a = 5 # 堆内存新存了5,a改指向5,地址改变
特点: 允许在原地修改数据。值变了,但内存地址(门牌号)不变。
lst = [1, 5, 8]# 此时 lst 指向堆内存中的一个列表对象# 修改 lst[0] = 3,列表本身的内存地址不会变
02 赋值的本质:只是贴标签
在 Python 中,赋值语句 (=) 永远不会创建数据的副本,它只是让变量引用了同一个对象。
请看这个经典案例:
a = [1, 5, 8]b = a # b 和 a 指向同一个内存地址b[0] = 3 # 修改 b 的元素print(a) # 输出: [3, 5, 8]
因为 list 是可变类型,b 和 a 共享同一个“房间”。b 在房间里搞破坏,a 进屋也能看到。03 核心难点:浅拷贝 vs 深拷贝
既然赋值只是引用,那如果我们想真正复制一份数据该怎么办?这里就涉及到了浅拷贝和深拷贝。
浅拷贝 (Shallow Copy)
定义: 也就是“影子克隆”。它会创建一个新的容器对象,但容器里面的元素,依然是指向旧对象里的引用。实现方式: 切片 [:]、copy.copy()、工厂函数 list() 等。
a = [1, 4, 5, [7, 8, 6]]b = a # 赋值:纯引用c = a[:] # 浅拷贝:c 是新壳,但里面的 [7,8,6] 还是旧的print(id(a) == id(b)) # Trueprint(id(a) == id(c)) # False (c有自己的地址)# 修改不可变元素(外层),c 不受影响c[0] = 99 # 修改可变嵌套元素(内层),a 会受影响!c[3].append(666) print(a) # 输出:[1, 4, 5, [7, 8, 6, 666]] # 惊!a 的内层列表也被改了
结论:浅拷贝只复制了第一层,对于嵌套的可变对象(如列表里的列表),它依然是藕断丝连。定义: “深度克隆”。不仅复制最外层的壳,还会递归地复制里面所有的子对象。完全独立,互不干扰。实现方式:import copy -> copy.deepcopy()
代码实测:
import copya = [1, 4, 5, [7, 8, 6]]d = copy.deepcopy(a)a[3].append(4) # 修改原列表print(a[3]) # [7, 8, 6, 4]print(d[3]) # [7, 8, 6] -> d 岿然不动
即使是不可变类型 Tuple,如果里面嵌套了 List,深拷贝也会把里面的 List 复制一份,保证完全隔离。垃圾回收 (Garbage Collection)
Python 怎么处理没用的变量?主要靠引用计数。
缓存机制 (Python 的优化)
为了提高效率,Python 对频繁使用的小对象有特殊的“优待”。
1. 小整数对象池 (Small Integer Cache)Python 默认将 -5 到 256 之间的整数预先创建好,常驻内存。
a = 3b = 3print(a is b) # True,因为都指向同一个预先创建好的 3
注:超出这个范围(例如 2345),在交互式命令行中可能会是两个不同的对象(id不同)。但在 PyCharm 或脚本文件中运行,编译器可能会做额外优化。a = 1234b = 1234print(a == b, a is b) # Truea = 2345b = 2344b += 1print(a == b, a is b) # False
对于 List、Dict、Tuple 等类型,当对象引用计数归零时,Python 不会立即把内存还给操作系统,而是放入一个 free_list 缓存区。 当下次再创建同类型对象时,直接从缓存区拿来用,避免反复向操作系统申请内存,从而提升效率。这也解释了为什么有时销毁对象后,新对象的内存地址还是原来的旧地址。总结
赋值是共享引用,改可变对象会互相影响。
浅拷贝只拷一层,嵌套的可变对象依然共享。
深拷贝彻底断交,完全独立。
小整数(-5~256) 全局唯一,Python 帮你存好了。
5.这里注意,当你使用Pandas库的时候,一些也有这样的原理
掌握这些,不仅能避开 Bug,更能让你理解 Python 运行的底层之美!
"Variables are assigned to objects; objects are not assigned to variables."(变量是分配给对象的,而不是对象分配给变量的。)