python中可变与不可变对象的核心区别在于内存中的值能否被原地修改:不可变对象(如int、str、tuple)修改时创建新对象,id改变;可变对象(如list、dict)可就地修改,id不变,影响所有引用。

Python中可变对象与不可变对象的核心区别,在于对象创建后其**内存中的值能否被原地修改**,这直接决定了赋值、函数传参、容器操作等行为的表现。理解这一点,关键要结合Python的“对象-引用”模型和底层内存管理机制。
不可变对象:值固定,修改即新建
常见的不可变对象包括 int、float、str、tuple、frozenset 等。它们的底层实现中,对象一旦创建,其内部数据(如字符串的字符序列、整数的二进制值)在内存中就被标记为只读;任何看似“修改”的操作(如 s += "x" 或 i += 1),实际都会触发新对象的创建,并让变量指向这个新地址。
- 例如:
a = "hello"; b = a; a += " world"后,a指向新字符串对象,b仍指向原对象,id(a) != id(b) - 不可变性使它们天然线程安全,也允许被用作字典的键或集合的元素
- CPython 中,小整数(-5 到 256)和短字符串有缓存机制,但这属于优化,不改变不可变语义
可变对象:内容可就地变更,引用保持不变
常见可变对象包括 list、dict、set、bytearray 等。它们的底层结构(如 list 的动态数组、dict 的哈希表)预留了修改空间,方法如 .append()、.update()、.add() 都直接操作原对象内存,不改变 id()。
- 例如:
l1 = [1, 2]; l2 = l1; l1.append(3)后,l2也变为[1, 2, 3],因为二者指向同一块内存 - 这种特性带来便利,也容易引发意外的副作用——尤其在函数参数传递时,若函数内修改了传入的 list,调用方看到的就是已被修改的结果
- 可变对象不能作为字典键,因为哈希值可能随内容变化而失效(
hash()要求稳定)
底层关键:对象头中的 ob_refcnt 与 ob_type
在 CPython 实现中,每个对象都以 PyObject 结构体开头,包含引用计数(ob_refcnt)和类型指针(ob_type)。是否可变,由类型对象(如 &PyList_Type 或 &PyString_Type)决定其支持哪些操作接口:
立即学习“Python免费学习笔记(深入)”;
- 不可变类型的
tp_as_sequence或tp_as_mapping中,赋值类方法(如sq_ass_item)通常为NULL,尝试修改会抛出TypeError - 可变类型则实现了这些接口,例如
list_setitem直接写入底层数组,不分配新对象 - GC(垃圾回收)仅追踪容器类型(如 list/dict),因为它们可能持有其他对象的引用;简单不可变对象(如 int/str)靠引用计数即可管理生命周期
实践建议:何时复制,何时共享
明确意图是避免 bug 的前提:
- 若需保留原始状态,对可变对象使用浅拷贝:
new_list = old_list.copy()或new_dict = old_dict.copy();嵌套结构需copy.deepcopy() - 函数设计时,如不希望影响入参,应在函数内主动复制(或文档注明“会修改原对象”)
- 构造字典键时,优先用
tuple(不可变)而非list;需要动态构建再转为键,可用tuple(my_list) - 性能敏感场景下,频繁拼接字符串应改用
list.append()+''.join(),避免因不可变性导致的多次内存分配
不复杂但容易忽略。真正掌握区别,不是背类型列表,而是看 id() 和 is 运算符的行为,再结合 CPython 源码中类型对象的定义方式去验证。









