在 Python 中,不可变类型(Immutable) 与 可变对象(Mutable) 的区别,是决定代码运行逻辑的最底层机制。
一、不可变类型(数字、字符串、元组)
核心机制:这类对象一旦在内存中创建,其内容就绝对无法被修改。当你试图“修改”它时,Python 实际上是在内存中创建了一个全新的对象,并把变量名(标签)重新贴到了这个新对象上。原对象如果没有被其他变量引用,就会等待垃圾回收。
1. 数字 (Int, Float)
x = 10
print(id(x)) # 假设输出内存地址:140725
x = x + 1 # 看起来是修改,其实是创建了新对象 11
print(id(x)) # 输出新地址:140726(地址变了,标签重贴了)2. 字符串 (String)
s = "hello"
# s[0] = "H" # ❌ 报错!TypeError,不允许直接修改字符串内部的字符
s = s.upper() # 产生了一个全新的大写字符串 "HELLO"
print(s) # 标签 s 现在指向了新创建的 "HELLO"3. 元组 (Tuple)
t = (1, 2)
# t[0] = 99 # ❌ 报错!TypeError
# ⚠️ 特殊陷阱:如果元组里装了“可变对象”(比如列表),列表内部是可以变的!
t_nested = (1, [2, 3])
t_nested[1].append(4) # 可以运行!元组本身没变(它存的列表内存地址没变),但列表变了
print(t_nested) # 输出: (1, [2, 3, 4])二、可变类型(列表、字典、集合)
核心机制:这类对象在内存中创建后,其内部内容可以被任意增删改,而对象本身的内存地址(ID)保持不变。这意味着,如果有多个变量(标签)贴在这个对象上,任何一个变量做出了修改,其他变量都会立刻感知到。
1. 列表 (List)
list_a = [1, 2, 3]
list_b = list_a # 两个标签贴在同一个列表上
list_a.append(4) # 直接修改原对象,内存地址不变
print(list_b) # 输出:[1, 2, 3, 4] (b 感知到了改变!)
print(id(list_a) == id(list_b)) # 输出:True2. 字典 (Dictionary)
user = {"name": "Alice"}
info = user # 共享同一个字典对象
user["age"] = 25 # 直接在原内存点添加键值对
print(info) # 输出:{'name': 'Alice', 'age': 25}3. 集合 (Set)
集合常用于去重和集合运算,它也是可变的,但由于其内部是用哈希表实现的,集合里只能存放不可变对象。
set_a = {1, 2}
set_b = set_a
set_a.add(3) # 修改原集合
print(set_b) # 输出:{1, 2, 3}💡 总结与防坑指南
- 传参陷阱:当你把一个列表传进函数时,函数内部的操作会直接影响到外部的原列表;但如果你传的是一个数字或字符串,函数内部无论怎么算,都绝对不会改变外部的变量。
- 深拷贝与浅拷贝:如何真正复制可变对象?如果你不希望别人影响你,不能直接 b = a,必须使用浅拷贝或深拷贝:
import copy
list_a = [1, 2, 3]
# 方式一:切片复制(浅拷贝)
list_b = list_a[:]
# 方式二:copy 模块
list_c = copy.deepcopy(list_a)