1. 什么是不可变数据类型?
在 python 中,不可变数据类型指的是对象一旦被创建,其值(或状态)就不能被改变。如果尝试修改一个不可变对象,python 实际上会创建一个新的对象,而不是在原有对象上直接修改。
python 中常见的不可变数据类型包括:
- int(整数)
- float(浮点数)
- str(字符串)
- bool(布尔值)
- tuple(元组)
- frozenset(冻结集合)
本文将重点讨论前四种最基础、最常用的不可变类型。
2. 整数(int)的不可变性
整数是 python 中最基本的数值类型之一,它完全不可变。
示例代码
# 示例 1:整数不可变验证
a = 10
print(f"a 的 id: {id(a)}") # 输出 a 的内存地址
# 尝试“修改”a
a = a + 5
print(f"修改后 a 的值: {a}")
print(f"修改后 a 的 id: {id(a)}") # id 已改变,说明是新对象
# 示例 2:小整数缓存(python 优化)
b = 10
c = 10
print(f"b 的 id: {id(b)}")
print(f"c 的 id: {id(c)}")
print(f"b 和 c 是同一个对象吗?{b is c}") # 通常为 true(-5 到 256 范围内)
示例 3:链式赋值与内存分配
a = b = 4 # a 和 b 都指向同一个整数对象 4
print(f"初始状态: a={a}, b={b}“)
print(f"a is b: {a is b}”) # true,指向同一对象
print(f"id(a): {id(a)}, id(b): {id(b)}")修改 b 的值
b = 5 # 为 b 创建新的整数对象 5,a 仍然指向 4
print(f"\n修改 b 后: a={a}, b={b}“)
print(f"a is b: {a is b}”) # false,指向不同对象
print(f"id(a): {id(a)}, id(b): {id(b)}“)
print(f"id(4): {id(4)}, id(5): {id(5)}”)验证小整数缓存
c = 4
print(f"\nc = 4, id©: {id©}“)
print(f"a is c: {a is c}”) # true,因为 4 在小整数缓存范围内关键特性
- 对整数进行任何运算(
+,-,*,//等)都会返回一个新的整数对象 - python 对小整数(通常为 -5 到 256)有缓存优化,相同值可能指向同一对象
- 大整数每次运算都会创建新对象
3. 浮点数(float)的不可变性
浮点数同样是不可变类型,所有浮点运算都会产生新对象。
示例代码
# 示例 1:浮点数不可变验证
x = 3.14
print(f"初始 x 的 id: {id(x)}")
x = x * 2
print(f"计算后 x 的值: {x}")
print(f"计算后 x 的 id: {id(x)}") # id 改变,新对象
# 示例 2:浮点数精度问题(与不可变性无关但重要)
print(0.1 + 0.2 == 0.3) # false,浮点数精度问题
print(f"0.1 + 0.2 = {0.1 + 0.2}") # 0.30000000000000004
关键特性
- 浮点运算可能产生精度误差,这是 ieee 754 标准特性,不是 python 独有
- 与整数不同,python 不会缓存浮点数对象(相同值的浮点数也可能是不同对象)
- 使用
decimal模块处理需要精确小数运算的场景
4. 字符串(str)的不可变性
字符串是 python 中最常用的不可变类型之一,所有字符串操作都返回新字符串。
示例代码
# 示例 1:字符串不可变验证
s = "hello"
print(f"初始 s 的 id: {id(s)}")
# 尝试修改字符串(实际创建新对象)
s = s + " world!"
print(f"修改后 s: {s}")
print(f"修改后 s 的 id: {id(s)}") # id 改变
# 示例 2:字符串方法返回新对象
text = "python"
upper_text = text.upper()
print(f"原字符串: {text}, id: {id(text)}")
print(f"大写后: {upper_text}, id: {id(upper_text)}")
print(f"是同一个对象吗?{text is upper_text}") # false
# 示例 3:字符串索引访问(只读)
word = "immutable"
try:
word[0] = "i" # 尝试修改第一个字符
except typeerror as e:
print(f"错误: {e}") # 'str' object does not support item assignment
关键特性
- 字符串的所有方法(
upper(),lower(),replace(),strip()等)都返回新字符串 - 字符串支持索引访问,但只能读取不能修改
- 字符串拼接(
+)或格式化(f-string,format())都会创建新对象 - 对于大量字符串拼接,建议使用
join()方法提高性能
5. 布尔值(bool)的不可变性
布尔值是特殊的整数子类(true 相当于 1,false 相当于 0),同样不可变。
示例代码
# 示例 1:布尔值不可变验证
flag = true
print(f"初始 flag 的 id: {id(flag)}")
# 布尔值本身不可变,但可以重新赋值
flag = false
print(f"重新赋值后 flag: {flag}")
print(f"重新赋值后 flag 的 id: {id(flag)}") # id 改变,指向 false 单例
# 示例 2:布尔值是单例对象
a = true
b = true
c = false
d = false
print(f"a is b: {a is b}") # true,同一对象
print(f"c is d: {c is d}") # true,同一对象
print(f"id(true): {id(true)}")
print(f"id(false): {id(false)}")
关键特性
true和false是单例对象,全局唯一- 布尔值是
int的子类:isinstance(true, int)返回true - 布尔运算(
and,or,not)返回新的布尔值
6. 不可变性的优势与注意事项
优势
- 线程安全:不可变对象可以在多线程环境中安全共享,无需加锁
- 哈希支持:不可变对象可哈希,可作为字典的键或集合的元素
- 缓存友好:相同值的不可变对象可以被缓存和重用
- 易于推理:对象状态不会意外改变,代码更可预测
注意事项
- 内存开销:频繁修改会创建大量新对象,可能增加内存压力
- 性能考虑:大量字符串拼接使用
join()而非+ - 容器中的不可变对象:列表中的不可变元素可以被替换,但元素本身不变
示例:字典键必须是不可变类型
# 有效:使用不可变类型作为键
valid_dict = {
"name": "alice", # str 键
42: "answer", # int 键
(1, 2): "tuple key", # tuple 键(不可变)
true: "boolean key" # bool 键
}
# 无效:尝试使用可变类型作为键
try:
invalid_dict = {[1, 2]: "list key"} # 列表不可哈希
except typeerror as e:
print(f"错误: {e}") # unhashable type: 'list'
7. 总结
python 的不可变数据类型(int, float, str, bool)是语言的基础构建块。理解它们的不可变性对于:
- 编写高效代码:避免不必要的对象创建
- 调试程序:理解对象标识(
id())与值(value)的区别 - 设计数据结构:正确使用字典键和集合元素
- 并发编程:利用不可变性实现线程安全
记住关键原则:对不可变对象的"修改"操作,实际上创建了新对象。这一特性使得 python 代码更加安全、可预测,同时也提醒我们在性能敏感的场景中要注意对象创建的开销。
8. 进一步学习
可变数据类型:list,dict,set
与不可变类型相反,python 中的可变数据类型允许在创建后修改其内容,而无需创建新对象。
列表(list)
my_list = [1, 2, 3]
print(f"初始 id: {id(my_list)}") # 初始内存地址
# 修改列表内容
my_list.append(4)
my_list[0] = 100
print(f"修改后列表: {my_list}")
print(f"修改后 id: {id(my_list)}") # id 不变,说明是同一个对象
字典(dict)
my_dict = {"name": "alice", "age": 25}
print(f"初始 id: {id(my_dict)}")
# 修改字典
my_dict["age"] = 26
my_dict["city"] = "beijing"
print(f"修改后字典: {my_dict}")
print(f"修改后 id: {id(my_dict)}") # id 不变
集合(set)
my_set = {1, 2, 3}
print(f"初始 id: {id(my_set)}")
# 修改集合
my_set.add(4)
my_set.remove(2)
print(f"修改后集合: {my_set}")
print(f"修改后 id: {id(my_set)}") # id 不变
关键区别:
- 可变对象:修改内容时,对象标识(
id())不变 - 不可变对象:任何"修改"都会创建新对象,
id()改变
浅拷贝与深拷贝:copy.copy()vscopy.deepcopy()
当需要复制可变对象时,理解拷贝的层次至关重要。
浅拷贝(shallow copy)
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
print(f"original id: {id(original)}")
print(f"shallow id: {id(shallow)}") # 外层列表是新对象
print(f"original[0] is shallow[0]: {original[0] is shallow[0]}") # true,内层列表是同一对象
# 修改浅拷贝的内层列表会影响原对象
shallow[0].append(99)
print(f"修改后 original: {original}") # [[1, 2, 99], [3, 4]]
print(f"修改后 shallow: {shallow}") # [[1, 2, 99], [3, 4]]
深拷贝(deep copy)
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
print(f"original id: {id(original)}")
print(f"deep id: {id(deep)}") # 外层列表是新对象
print(f"original[0] is deep[0]: {original[0] is deep[0]}") # false,内层列表也是新对象
# 修改深拷贝不会影响原对象
deep[0].append(99)
print(f"修改后 original: {original}") # [[1, 2], [3, 4]],保持不变
print(f"修改后 deep: {deep}") # [[1, 2, 99], [3, 4]]
使用场景:
copy.copy():当对象只包含不可变元素或你明确知道需要共享内部引用时copy.deepcopy():当对象包含嵌套的可变对象,且你需要完全独立的副本时
id()函数:查看对象内存地址
id() 函数返回对象的唯一标识符(通常是内存地址),用于判断两个变量是否指向同一个对象。
# 不可变对象示例
a = 100
b = 100
c = 200
d = 200
print(f"id(a): {id(a)}, id(b): {id(b)}")
print(f"a is b: {a is b}") # true(小整数缓存)
print(f"id(c): {id(c)}, id(d): {id(d)}")
print(f"c is d: {c is d}") # false(大整数无缓存)
# 可变对象示例
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1
print(f"\nid(list1): {id(list1)}")
print(f"id(list2): {id(list2)}")
print(f"id(list3): {id(list3)}")
print(f"list1 is list2: {list1 is list2}") # false,内容相同但不是同一对象
print(f"list1 is list3: {list1 is list3}") # true,指向同一对象
重要提示:
id()的值在对象的生命周期内是唯一的- 两个对象的
id()相同意味着它们是同一个对象 - cpython 中
id()返回的是对象的内存地址,但这不是语言规范要求
is与==的区别:对象标识 vs 值相等
这是 python 中容易混淆但非常重要的概念。
is 运算符:检查两个变量是否指向同一个对象(比较对象标识)== 运算符:检查两个对象的值是否相等(比较对象内容)
# 示例 1:不可变对象(小整数)
x = 256
y = 256
print(f"x == y: {x == y}") # true,值相等
print(f"x is y: {x is y}") # true,小整数缓存指向同一对象
# 示例 2:不可变对象(大整数)
a = 1000
b = 1000
print(f"\na == b: {a == b}") # true,值相等
print(f"a is b: {a is b}") # false,大整数无缓存,不同对象
# 示例 3:可变对象
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1
print(f"\nlist1 == list2: {list1 == list2}") # true,内容相同
print(f"list1 is list2: {list1 is list2}") # false,不同对象
print(f"list1 is list3: {list1 is list3}") # true,同一对象
# 示例 4:特殊值
print(f"\nnone == none: {none == none}") # true
print(f"none is none: {none is none}") # true,none 是单例
print(f"true == 1: {true == 1}") # true,值相等
print(f"true is 1: {true is 1}") # false,不同类型
使用建议:
- 比较值是否相等时,使用
== - 检查是否为
none时,总是使用is或is not - 检查两个变量是否指向同一对象时,使用
is - 对于布尔值
true/false,通常使用==比较值,除非明确需要检查对象标识
性能考虑:is 通常比 == 更快,因为它只比较内存地址,而不需要比较对象内容。
以上就是python中不可变数据类型int, float, str, bool详解的详细内容,更多关于python不可变数据类型的资料请关注代码网其它相关文章!
发表评论