Python变量
引用机制
在 Python 中,变量本身不存储数据,它更像一个 "标签" 或 "指针",指向内存中真正存储数据的对象。
- 当你执行
a = 10时,Python 会先在内存中创建一个值为 10 的int对象,然后让变量a成为这个对象的 "引用者"(即a指向该对象)。 - 当你再执行
b = a时,并不会复制 10 这个值,而是让变量b也指向同一个int对象(a和b是同一个对象的两个引用)。
如何实现
Python 中的 "变量名"(如a、b)本身不直接关联数据,而是作为符号表(symbol table)中的一个 "键",其对应的值是一个内存地址指针(指向真正的对象)。
- 符号表的本质:是一个哈希表(类似字典),存储在特定作用域中(模块、函数、类等都有自己的符号表)。例如:
- 执行
a = 10时,CPython 会在当前作用域的符号表中添加一条记录:{"a": 0x7f8a1b2c3d4e}(其中0x7f8a1b2c3d4e是整数 10 的对象在内存中的地址)。 - 执行
b = a时,符号表会新增{"b": 0x7f8a1b2c3d4e}——b的指针与a完全相同,指向同一个对象。
- 执行
- 作用域与符号表:不同作用域(全局、局部、嵌套)有独立的符号表。例如函数内部的变量存储在局部符号表中,函数执行结束后,局部符号表被销毁,变量名对应的指针失效(可能触发对象引用计数减少)。
引用计数的增减规则
- 增加:
- 变量赋值:
a = obj(obj的引用计数 + 1); - 变量作为参数传递:
func(obj)(函数调用时,obj的引用计数 + 1); - 变量被放入容器:
list.append(obj)(容器会新增一个对obj的引用,计数 + 1)。
- 变量赋值:
- 减少:
- 变量被重新赋值:
a = other_obj(原对象的引用计数 - 1); - 变量超出作用域:函数执行结束,内部变量失效(引用计数 - 1);
- 变量被显式删除:
del a(a指向的对象引用计数 - 1); - 对象从容器中移除:
list.remove(obj)(容器对obj的引用消失,计数 - 1)。
- 变量被重新赋值:
引用传递:指针的复制
当我们进行赋值、传参等操作时,"引用传递" 的本质是指针的复制,而非数据的复制。
- 赋值操作:
b = a会将a在符号表中对应的指针(如0x7f8a1b2c3d4e)复制给b,此时a和b的符号表条目指向同一个内存地址,即引用同一个对象。 - 函数传参:
def func(x): ...; func(a)会将a的指针复制给参数x,函数内部的x与外部的a指向同一个对象(因此修改可变对象时,外部能感知到变化)。 - 容器存储:
lst = [a]会在列表的内部数组中存储a的指针,列表对a指向的对象产生一个新的引用(因此对象的ob_refcnt会 + 1)。
可变对象与不可变对象
在 Python 中,可变对象(Mutable Objects)和不可变对象(Immutable Objects)的核心区别在于:对象创建后,其内部数据是否可以被 "原地修改"。
- 可变对象:list, dict, set 等,支持原地修改
- 不可变对象:int, str, tuple 等,不支持原地修改
小整数缓存机制
Python(主要是 CPython)会预先创建并缓存一定范围的整数对象(范围 [-5, 256]),避免频繁创建和销毁的开销。
字符串 intern 机制
Python 的intern机制(字符串驻留)本质是一种字符串去重优化,通过将相同内容的字符串映射到内存中唯一的对象,实现内存复用和比较加速。
深浅拷贝
- 浅拷贝:对于不可变类型直接复用,对于可变类型创建新对象,但嵌套的可变对象仍然是引用
- 深拷贝:创建完全独立的对象,包括嵌套的可变对象