可变对象允许原地修改内容且内存地址不变,如列表、字典;不可变对象一旦创建内容不可变,任何“修改”都生成新对象,如整数、字符串、元组。该区别影响变量赋值、函数传参及数据结构使用,尤其在函数中对可变参数的原地修改会影响外部对象,而不可变对象则不会;此外,只有不可变对象才能作为字典键或集合元素,因其哈希值需稳定,确保哈希表机制正常工作。

Python中的可变对象和不可变对象,简单来说,核心区别在于它们被创建之后,其内部状态能否被修改。可变对象允许你在不改变其内存地址的前提下修改其内容,而不可变对象一旦创建,其值就固定了,任何看起来是“修改”的操作,实际上都是创建了一个新的对象。理解这一点,对于我们在Python中处理数据、避免一些隐蔽的bug,以及优化代码性能,都至关重要。
在Python的世界里,对象的这种“变”与“不变”属性,是其数据模型中一个非常基础但又极其关键的特性。它不仅仅是理论上的概念,更是实实在在影响我们日常编码行为的。
不可变对象 (Immutable Objects) 这类对象,一旦创建,其内部状态(值)就不能被改变。如果你尝试修改一个不可变对象,Python并不会在原地修改它,而是会创建一个新的对象,并将变量引用指向这个新对象。 常见的不可变对象包括:
int
float
complex
bool
str
tuple
frozenset
举个例子,当我们写
a = 10
a = a + 1
a
a
a + 1
a
可变对象 (Mutable Objects) 与不可变对象相反,可变对象在创建后,其内部状态可以被修改,而且这种修改是“原地”进行的,也就是说,对象的内存地址通常不会改变。 常见的可变对象包括:
list
dict
set
bytearray
比如,
my_list = [1, 2, 3]
my_list.append(4)
id()
立即学习“Python免费学习笔记(深入)”;
我觉得,这个区分之所以重要,是因为它直接影响到我们对变量赋值、函数参数传递以及数据结构行为的理解。有时候,我们可能会因为对这个概念的模糊,而写出一些有意外副作用的代码,尤其是在函数调用或者多线程环境中。
判断一个对象是可变还是不可变,其实有好几种方法,有些直观,有些则需要一点点代码验证。我个人最常用的,也是最直接的方法,就是利用
id()
1. 使用 id()
id()
对于不可变对象: 任何“修改”操作后,对象的
id()
num = 10
print(f"原始数字的ID: {id(num)}") # 比如:140737352316480
num = num + 1 # 看起来是修改,实则创建新对象
print(f"修改后数字的ID: {id(num)}") # 比如:140737352316512 (ID变了)
s = "hello"
print(f"原始字符串的ID: {id(s)}") # 比如:2346048560304
s += " world" # 同样是创建新字符串
print(f"修改后字符串的ID: {id(s)}") # 比如:2346048560464 (ID变了)对于可变对象: 在进行原地修改操作(如
append
extend
pop
update
id()
my_list = [1, 2, 3]
print(f"原始列表的ID: {id(my_list)}") # 比如:2346048560640
my_list.append(4) # 原地修改
print(f"修改后列表的ID: {id(my_list)}") # 比如:2346048560640 (ID不变)
my_dict = {'a': 1}
print(f"原始字典的ID: {id(my_dict)}") # 比如:2346048560768
my_dict['b'] = 2 # 原地修改
print(f"修改后字典的ID: {id(my_dict)}") # 比如:2346048560768 (ID不变)这里有个小陷阱,如果你对可变对象进行赋值操作,比如
my_list = [5, 6]
my_list
id()
2. 查阅文档或记住常见类型: 这是最省事的方法。Python的官方文档对每种内置类型都有详细说明。一般来说:
int
float
str
tuple
frozenset
list
dict
set
bytearray
3. 尝试调用修改方法: 如果一个对象有
append()
extend()
insert()
pop()
remove()
sort()
add()
update()
clear()
replace()
我个人觉得,对于初学者来说,先记住那些常见的可变和不可变类型,然后通过
id()
这本书给出了一份关于python这门优美语言的精要的参考。作者通过一个完整而清晰的入门指引将你带入python的乐园,随后在语法、类型和对象、运算符与表达式、控制流函数与函数编程、类及面向对象编程、模块和包、输入输出、执行环境等多方面给出了详尽的讲解。如果你想加入 python的世界,David M beazley的这本书可不要错过哦。 (封面是最新英文版的,中文版貌似只译到第二版)
1
在Python中,函数参数传递采用的是“传对象引用”(pass-by-object-reference)的机制,这和C++的“传值”或“传引用”有所不同,它介于两者之间,但又独具特色。可变对象和不可变对象在这个机制下的行为差异,是很多Python新手容易混淆,甚至老手也偶尔会踩坑的地方。
1. 传递不可变对象作为参数: 当你将一个不可变对象(如整数、字符串、元组)作为参数传递给函数时,函数内部对这个参数的任何“修改”操作,实际上都会在函数内部创建一个新的局部变量,并让这个局部变量指向新的对象。原始的外部对象不会受到任何影响。
def modify_immutable(num_param, str_param):
print(f"函数内部 - 原始数字ID: {id(num_param)}")
num_param += 1 # 实际是创建了一个新的整数对象,num_param指向它
print(f"函数内部 - 修改后数字ID: {id(num_param)}")
print(f"函数内部 - 修改后数字: {num_param}")
print(f"函数内部 - 原始字符串ID: {id(str_param)}")
str_param += " world" # 实际是创建了一个新的字符串对象
print(f"函数内部 - 修改后字符串ID: {id(str_param)}")
print(f"函数内部 - 修改后字符串: {str_param}")
my_num = 10
my_str = "hello"
print(f"函数外部 - 原始数字ID: {id(my_num)}")
print(f"函数外部 - 原始字符串ID: {id(my_str)}")
modify_immutable(my_num, my_str)
print(f"函数外部 - 调用后数字: {my_num}, ID: {id(my_num)}") # 外部my_num不变
print(f"函数外部 - 调用后字符串: {my_str}, ID: {id(my_str)}") # 外部my_str不变你会发现,函数内部
num_param
str_param
id
my_num
my_str
id
num_param += 1
2. 传递可变对象作为参数: 当你将一个可变对象(如列表、字典、集合)作为参数传递给函数时,函数内部和外部的变量都指向同一个对象。因此,如果在函数内部对这个可变对象进行“原地修改”操作(例如
list.append()
dict.update()
def modify_mutable(list_param, dict_param):
print(f"函数内部 - 原始列表ID: {id(list_param)}")
list_param.append(4) # 原地修改,外部列表也会受影响
print(f"函数内部 - 修改后列表ID: {id(list_param)}")
print(f"函数内部 - 修改后列表: {list_param}")
print(f"函数内部 - 原始字典ID: {id(dict_param)}")
dict_param['c'] = 3 # 原地修改,外部字典也会受影响
print(f"函数内部 - 修改后字典ID: {id(dict_param)}")
print(f"函数内部 - 修改后字典: {dict_param}")
my_list = [1, 2, 3]
my_dict = {'a': 1, 'b': 2}
print(f"函数外部 - 原始列表ID: {id(my_list)}")
print(f"函数外部 - 原始字典ID: {id(my_dict)}")
modify_mutable(my_list, my_dict)
print(f"函数外部 - 调用后列表: {my_list}, ID: {id(my_list)}") # 外部my_list已改变
print(f"函数外部 - 调用后字典: {my_dict}, ID: {id(my_dict)}") # 外部my_dict已改变这里,函数内部的
list_param
dict_param
id
my_list
my_dict
一个重要的例外:在函数内部重新赋值可变参数 如果我们在函数内部,对传入的可变参数进行重新赋值操作(例如
list_param = [5, 6]
list_param
my_list
def reassign_mutable(list_param):
print(f"函数内部 - 原始列表ID: {id(list_param)}")
list_param = [5, 6] # 重新赋值,list_param现在指向一个新对象
print(f"函数内部 - 重新赋值后列表ID: {id(list_param)}")
print(f"函数内部 - 重新赋值后列表: {list_param}")
my_list_reassign = [1, 2, 3]
print(f"函数外部 - 原始列表ID: {id(my_list_reassign)}")
reassign_mutable(my_list_reassign)
print(f"函数外部 - 调用后列表: {my_list_reassign}, ID: {id(my_list_reassign)}") # 外部my_list_reassign不变这块儿确实容易踩坑,因为它模糊了“原地修改”和“重新赋值”的区别。我个人觉得,理解这个点对于编写健壮的Python代码非常关键,尤其是在设计函数接口时,要清楚函数是否会修改传入的可变参数,避免产生意料之外的副作用。如果不想函数修改原始的可变对象,可以考虑在传入前先创建一份副本(例如
my_list[:]
list(my_list)
这是一个非常好的问题,它触及到了Python中字典(
dict
set
哈希值与哈希表 字典和集合为了实现高效的查找、插入和删除操作,都依赖于哈希表这种数据结构。当我们把一个对象作为字典的键或集合的元素时,Python会计算这个对象的哈希值。这个哈希值可以看作是对象在内存中存储位置的一个“指纹”或“索引”。
__eq__
哈希值的稳定性要求 想象一下,如果一个可变对象(比如一个列表
[1, 2]
[1, 2]
my_list.append(3)
[1, 2, 3]
问题就在这里:
[1, 2, 3]
[1, 2]
为了避免这种灾难性的后果,Python明确规定,只有哈希值在对象生命周期内保持不变的对象,才能作为字典的键或集合的元素。而只有不可变对象才能保证这一点,因为它们的内部状态一旦创建就不能改变,所以它们的哈希值也是固定的。
Python的强制执行 如果你尝试将一个可变对象(如列表或字典)作为字典的键或集合的元素,Python会直接抛出一个
TypeError
unhashable type: 'list'
unhashable type: 'dict'
# 错误示例
my_list_key = [1, 2]
# my_dict = {my_list_key: "value"} # 这会引发 TypeError
# 错误示例
my_set = set()
# my_set.add([1, 2]) # 这也会引发 TypeError__hash__
__eq__
__hash__
obj1 == obj2
True
hash(obj1) == hash(obj2)
不可变对象天生满足这些条件,因为它们的值不会变。而可变对象,由于其值可能变化,因此通常不实现
__hash__
__hash__
TypeError
frozenset
frozenset
set
在我看来,这个设计体现了Python在实用性和数据完整性之间的权衡。虽然限制了可变对象的使用场景,但它确保了字典和集合这些核心数据结构的高效性和可靠性,避免了潜在的复杂问题。理解这一点,能帮助我们更好地选择合适的数据结构来解决问题。
以上就是python中可变对象和不可变对象是什么?的详细内容,更多请关注php中文网其它相关文章!
python怎么学习?python怎么入门?python在哪学?python怎么学才快?不用担心,这里为大家提供了python速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号