ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

深入理解 Python 不可变数据类型:int, float, str, bool

深入理解 Python 不可变数据类型:int, float, str, bool

1. 什么是不可变数据类型?

在 Python 中,不可变数据类型指的是对象一旦被创建,其值(或状态)就不能被改变。如果尝试修改一个不可变对象,Python 实际上会创建一个新的对象,而不是在原有对象上直接修改。

Python 中常见的不可变数据类型包括:

  • int(整数)
  • float(浮点数)
  • str(字符串)
  • bool(布尔值)
  • tuple(元组)
  • frozenset(冻结集合)

本文将重点讨论前四种最基础、最常用的不可变类型。

2. 整数(int)的不可变性

整数是 Python 中最基本的数值类型之一,它完全不可变。

示例代码

# 示例 1:整数不可变验证a=10print(f"a 的 id:{id(a)}")# 输出 a 的内存地址# 尝试“修改”aa=a+5print(f"修改后 a 的值:{a}")print(f"修改后 a 的 id:{id(a)}")# id 已改变,说明是新对象# 示例 2:小整数缓存(Python 优化)b=10c=10print(f"b 的 id:{id(b)}")print(f"c 的 id:{id(c)}")print(f"b 和 c 是同一个对象吗?{bisc}")# 通常为 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.14print(f"初始 x 的 id:{id(x)}")x=x*2print(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"是同一个对象吗?{textisupper_text}")# False# 示例 3:字符串索引访问(只读)word="immutable"try:word[0]="I"# 尝试修改第一个字符exceptTypeErrorase: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=Trueprint(f"初始 flag 的 id:{id(flag)}")# 布尔值本身不可变,但可以重新赋值flag=Falseprint(f"重新赋值后 flag:{flag}")print(f"重新赋值后 flag 的 id:{id(flag)}")# id 改变,指向 False 单例# 示例 2:布尔值是单例对象a=Trueb=Truec=Falsed=Falseprint(f"a is b:{aisb}")# True,同一对象print(f"c is d:{cisd}")# True,同一对象print(f"id(True):{id(True)}")print(f"id(False):{id(False)}")

关键特性

  • TrueFalse是单例对象,全局唯一
  • 布尔值是int的子类:isinstance(True, int)返回True
  • 布尔运算(and,or,not)返回新的布尔值

6. 不可变性的优势与注意事项

优势

  1. 线程安全:不可变对象可以在多线程环境中安全共享,无需加锁
  2. 哈希支持:不可变对象可哈希,可作为字典的键或集合的元素
  3. 缓存友好:相同值的不可变对象可以被缓存和重用
  4. 易于推理:对象状态不会意外改变,代码更可预测

注意事项

  1. 内存开销:频繁修改会创建大量新对象,可能增加内存压力
  2. 性能考虑:大量字符串拼接使用join()而非+
  3. 容器中的不可变对象:列表中的不可变元素可以被替换,但元素本身不变

示例:字典键必须是不可变类型

# 有效:使用不可变类型作为键valid_dict={"name":"Alice",# str 键42:"Answer",# int 键(1,2):"tuple key",# tuple 键(不可变)True:"Boolean key"# bool 键}# 无效:尝试使用可变类型作为键try:invalid_dict={[1,2]:"list key"}# 列表不可哈希exceptTypeErrorase:print(f"错误:{e}")# unhashable type: 'list'

7. 总结

Python 的不可变数据类型(int,float,str,bool)是语言的基础构建块。理解它们的不可变性对于:

  1. 编写高效代码:避免不必要的对象创建
  2. 调试程序:理解对象标识(id())与值(value)的区别
  3. 设计数据结构:正确使用字典键和集合元素
  4. 并发编程:利用不可变性实现线程安全

记住关键原则:对不可变对象的"修改"操作,实际上创建了新对象。这一特性使得 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]=100print(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"]=26my_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)

importcopy 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]isshallow[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)

importcopy 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]isdeep[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=100b=100c=200d=200print(f"id(a):{id(a)}, id(b):{id(b)}")print(f"a is b:{aisb}")# True(小整数缓存)print(f"id(c):{id(c)}, id(d):{id(d)}")print(f"c is d:{cisd}")# False(大整数无缓存)# 可变对象示例list1=[1,2,3]list2=[1,2,3]list3=list1print(f"\nid(list1):{id(list1)}")print(f"id(list2):{id(list2)}")print(f"id(list3):{id(list3)}")print(f"list1 is list2:{list1islist2}")# False,内容相同但不是同一对象print(f"list1 is list3:{list1islist3}")# True,指向同一对象

重要提示

  • id()的值在对象的生命周期内是唯一的
  • 两个对象的id()相同意味着它们是同一个对象
  • CPython 中id()返回的是对象的内存地址,但这不是语言规范要求

is==的区别:对象标识 vs 值相等

这是 Python 中容易混淆但非常重要的概念。

is运算符:检查两个变量是否指向同一个对象(比较对象标识)
==运算符:检查两个对象的值是否相等(比较对象内容)

# 示例 1:不可变对象(小整数)x=256y=256print(f"x == y:{x==y}")# True,值相等print(f"x is y:{xisy}")# True,小整数缓存指向同一对象# 示例 2:不可变对象(大整数)a=1000b=1000print(f"\na == b:{a==b}")# True,值相等print(f"a is b:{aisb}")# False,大整数无缓存,不同对象# 示例 3:可变对象list1=[1,2,3]list2=[1,2,3]list3=list1print(f"\nlist1 == list2:{list1==list2}")# True,内容相同print(f"list1 is list2:{list1islist2}")# False,不同对象print(f"list1 is list3:{list1islist3}")# True,同一对象# 示例 4:特殊值print(f"\nNone == None:{None==None}")# Trueprint(f"None is None:{NoneisNone}")# True,None 是单例print(f"True == 1:{True==1}")# True,值相等print(f"True is 1:{Trueis1}")# False,不同类型

使用建议

  1. 比较值是否相等时,使用==
  2. 检查是否为None时,总是使用isis not
  3. 检查两个变量是否指向同一对象时,使用is
  4. 对于布尔值True/False,通常使用==比较值,除非明确需要检查对象标识

性能考虑is通常比==更快,因为它只比较内存地址,而不需要比较对象内容。

range

Python3 range() 函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表。

Python3 list() 函数是对象迭代器,可以把 range() 返回的可迭代对象转为一个列表,返回的变量类型为列表。

Python2 range() 函数返回的是列表。

函数语法
range(stop)
range(start, stop[, step])
参数说明:

start: 计数从 start 开始。默认是从 0 开始。例如 range(5) 等价于 range(0, 5)
stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是 [0, 1, 2, 3, 4] 没有 5
step:步长,默认为 1。例如:range(0, 5) 等价于 range(0, 5, 1)
for number in range(6, 1, -1):
print(number)

for i in range(-10, -100, -30) :
print(i)

-10
-40
-70

while

类型

()【】 {} {:,}
定义,增删查改
推导试

dict

defaultdict(int) list:解决key不存在的情况,默认赋值

d = {key1 : value1, key2 : value2, key3 : value3 }

[表达式 for 变量 in 列表]
[out_exp_res for out_exp in input_list]

或者

[表达式 for 变量 in 列表 if 条件]
[out_exp_res for out_exp in input_list if condition]

{ key_expr: value_expr for value in collection }

{ key_expr: value_expr for value in collection if condition }

{ expression for item in Sequence }

{ expression for item in Sequence if conditional }

装饰器

语法糖
装饰器通过 @ 语法糖应用在函数定义前:
解析:

decorator_function:装饰器函数(接收原函数)
wrapper:包装函数(真正被执行)
@decorator_function:等价于函数替换

def decorator_function(original_function):
def wrapper(*args, **kwargs):
# 调用前
print(“执行前”)

result = original_function(*args, **kwargs) # 调用后 print("执行后") return result return wrapper

@decorator_function
def target_function():
print(“原函数执行”)

调用 target_function() 时,实际上执行的是 wrapper()

if elif if

if condition_1:
statement_block_1
elif condition_2:
statement_block_2
else:
statement_block_3

try except else finally

try:
runoob()
except AssertionError as error:
print(error)
else:
try:
with open(‘file.log’) as file:
read_data = file.read()
except FileNotFoundError as fnf_error:
print(fnf_error)
finally:
print(‘这句话,无论异常是否发生都会执行。’)

TypeError:操作或函数应用于不适当类型的对象时引发。例如,尝试将整数和字符串相加123。
ValueError:传入有效类型但值不合适的参数时引发。例如,尝试将非数字字符串转换为整数1。
NameError:尝试访问未定义的变量时引发23。
IndexError:序列索引超出范围时引发。例如,尝试访问列表中不存在的索引12。
KeyError:字典中不存在的键时引发1。
ZeroDivisionError:尝试除以零时引发12。 IOError:尝试读取不存在的文件或无法访问文件时引发2。 ImportError:尝试导入不存在的模块或包时引发2。
AttributeError:尝试访问对象不存在的属性时引发2。
KeyboardInterrupt:用户中断程序执行时引发。例如,按下Ctrl+C时2。
SyntaxError:代码不符合Python语法规则时触发。例如,缩进错误、未闭合的字符串引号、错误的关键字拼写等3。
IndentationError:代码的缩进格式不符合语法规范时触发。例如,混合使用空格和制表符、缩进层级不一致等3。

Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。

Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。

返回列表