理解 Python 的内存模型,比死记“传值还是传引用”更有用。变量名保存在栈侧,真正的对象在堆上;赋值改的是“指向谁”,就地修改改的是“对象内部”。本篇先建立心智模型,再对比不可变对象与可变对象的内存行为。
本篇目录
1. 先建立心智模型
2. 不可变对象的内存行为
3. 可变对象的内存行为
1. 先建立心智模型
Python 中几乎一切都是对象。观察一个对象,通常看三件事:
教学图里常把内存画成:
• 栈:保存变量名,以及它当前指向的对象标识
• 堆:保存真正的对象(类型、值、内部引用)
变量更像标签,不是“装满数据的盒子”。a = b 的含义是:让 a 与 b 指向同一个对象,而不是复制一份新的内容。
注意:id() 的具体数值只在当前进程、当前时刻有意义,不同运行会变化;下文配图中的地址是示意编号,不要当成固定常量。
2. 不可变对象的内存行为
2.1 哪些对象是不可变的
不可变对象不能在原地改内容;“修改”通常意味着创建新对象,再让变量指向新对象。
常见不可变类型:
• int
• float
• bool
• str
• tuple
2.2 赋值:变量绑定到对象
name = "lili"print("内存地址", id(name))print("类型", type(name))print("值", name)执行后,栈上的 name 保存一个引用,堆上存在一个字符串对象,包含 id、type=str、value='lili'。
2.3 重新赋值:旧对象不再被当前变量引用
name = "lili"name = "feifei"第二次赋值不会改写 'lili' 本身,而是:
1. 在堆上创建新的字符串对象 'feifei'
2. 让 name 指向新对象
3. 原来的 'lili' 若没有其他引用,后续可由垃圾回收回收
可用代码验证“换了对象”:
name = "lili"old_id = id(name)name = "feifei"print(old_id == id(name)) # False2.4 别名:多个变量指向同一不可变对象
name = "lili"name2 = nameprint(name is name2) # Truename2 = name 不会复制字符串,只是再贴一张标签。两个变量指向同一个堆对象。
因为对象不可变,通过任一别名都无法改坏另一个变量“眼里的内容”;真正危险的是可变对象的别名共享。
3. 可变对象的内存行为
3.1 哪些对象是可变的
可变对象允许在保持对象身份不变的前提下,改写内部数据。
常见可变类型:
1. list
2. dict
3. set
3.2 创建列表
栈上的 lst 指向堆上的列表对象。列表内部再保存对各个元素对象的引用。
3.3 就地修改:地址通常不变
lst = [23, 56, 78]before = id(lst)lst[1] = 66after = id(lst)print(before == after) # Trueprint(lst) # [23, 66, 78]lst[1] = 66 改的是列表内部第 1 个位置指向谁,列表对象本身的身份不变。因此:
• id(lst) 通常保持不变
• 所有指向该列表的别名,都会立刻看到新内容
对比一下“重新绑定”:
lst = [23, 56, 78]lst = [23, 66, 78] # 这是换了一个全新的列表对象前者是原地改;后者是让变量指向另一个列表。