
Python 中的不可变对象 vs 可变对象
在Python中,所有的对象都可以归为两类:可变对象和不可变对象。这两种类型最大的区别在于,是否可以在对象创建后修改它的值。
-
不可变对象(Immutable Objects):一旦创建,内容就无法被修改。如果要改变值,就必须创建一个新的对象。 -
可变对象(Mutable Objects):可以在原地修改内容,甚至不需要改变对象的内存地址。
比如我们看下面这个例子:
# 不可变对象例子:整数(int)
x = 10
print(id(x)) # 输出对象的内存地址
x = 20
print(id(x)) # 地址会发生变化,说明x指向了一个新对象
# 运行结果:
# 139752148232144
# 139752148232496
# 可变对象例子:列表(list)
y = [1, 2, 3]
print(id(y)) # 输出列表对象的内存地址
y.append(4) # 向列表中添加一个元素
print(id(y)) # 地址不变,说明列表内容是在原地修改的
# 运行结果:
# 139752148233840
# 139752148233840
上面这个例子中,x
是一个整数,它是不可变对象。当你修改x
的值时,实际上是创建了一个新的对象,并且x
指向了新地址。而y
是一个列表,是可变对象。我们在原地修改了列表,内存地址却没有发生变化。
为什么要区分这两者?
这涉及到性能和数据管理。当你在程序中传递对象时,如果是可变对象,你修改了这个对象的内容,其他引用到它的地方也会受到影响。但如果是不可变对象,你可以放心地传递,不用担心其他地方会被意外修改。
Python中的不可变对象内置类型
在Python中,有几种常见的不可变对象类型。这些对象在创建之后,无法修改其内容。
整数(int)
整数是不可变的,每次对一个整数进行操作,都会创建一个新的整数对象。
例如:
# 整数(int)
a = 10
print(id(a)) # 打印a的内存地址
a += 5
print(id(a)) # 修改a的值,内存地址变化
140707704477264 # 第一次打印地址
140707704477424 # 第二次打印地址,变化了
在这里,a += 5
并不是直接修改a
的值,而是创建了一个新对象,然后将a
指向新对象。
字符串(str)
字符串也是不可变的,任何对字符串的修改都会生成新的字符串对象。
s = "hello"
print(id(s)) # 初始内存地址
s = s + " world"
print(id(s)) # 连接字符串后,内存地址变化
140707738801456 # 第一次打印地址
140707738801680 # 第二次打印地址,变化了
尽管看起来我们是在修改字符串,但实际上是创建了一个新字符串。
元组(tuple)
元组是不可变的序列,一旦定义了元组的内容,就无法修改它。
t = (1, 2, 3)
print(id(t))
t = (4, 5, 6)
print(id(t)) # 重新赋值后,内存地址变化
140707738769088 # 第一次打印地址
140707738794528 # 第二次打印地址,变化了
虽然元组里的元素是不可变的,但要注意,如果元组中包含可变对象(比如列表),这些可变对象本身是可以改变的。
布尔(bool)
布尔类型True
和False
也是不可变的,和整数一样,它们是固定的,无法更改。
b = True
print(id(b)) # 布尔类型的内存地址
94805411839808 # 布尔True的固定内存地址
浮点数(float)
浮点数也是不可变类型,类似于整数,每次进行算术运算都会生成一个新的浮点数对象。
f = 3.14
print(id(f)) # 浮点数的内存地址
f += 1.0
print(id(f)) # 修改值后,内存地址变化
140707704478960 # 第一次打印地址
140707704478832 # 第二次打印地址,变化了
不可变对象包括整数、字符串、元组、布尔和浮点数等。这些对象在创建后无法直接修改,如果需要更改它们的值,必须创建一个新的对象。下面我们再看一下Python可变对象内置类型
Python中的可变对象内置类型
与不可变对象不同,可变对象可以在创建后直接修改它们的内容,而不必生成新的对象。Python 中的常见可变对象类型包括列表、字典、集合等。
列表(list)
列表是一个可以容纳多个元素的容器,列表中的元素可以被直接修改或新增,而不需要创建新列表。
my_list = [1, 2, 3]
print(id(my_list)) # 列表的内存地址
my_list.append(4) # 修改列表内容,添加一个新元素
print(id(my_list)) # 内存地址不变,列表被修改了
这里,append
操作直接修改了列表,列表的内存地址没有发生变化。
140707738877760 # 第一次打印地址
140707738877760 # 第二次打印地址,未变化
字典(dict)
字典是一种键值对结构,可以随时修改键值对内容、添加新键值对,或者删除现有的键值对。
my_dict = {'a': 1, 'b': 2}
print(id(my_dict)) # 字典的内存地址
my_dict['c'] = 3 # 添加一个新的键值对
print(id(my_dict)) # 内存地址不变,字典被修改了
在这里,我们直接修改了字典的内容,但字典对象的地址保持不变。
140707738884928 # 第一次打印地址
140707738884928 # 第二次打印地址,未变化
集合(set)
集合是一个无序、不重复元素的集合,可以添加或删除元素。
my_set = {1, 2, 3}
print(id(my_set)) # 集合的内存地址
my_set.add(4) # 添加一个元素
print(id(my_set)) # 内存地址不变,集合被修改了
同样,集合也是可变的,对它的修改不会导致新对象的生成。
140707738788352 # 第一次打印地址
140707738788352 # 第二次打印地址,未变化
自定义对象
除了内置类型,Python 中我们可以定义自己的类,并决定是否让类的实例是可变的。通常情况下,自定义类对象是可变的,因为类的属性可以动态修改。
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass(10)
print(id(obj)) # 自定义对象的内存地址
obj.value = 20 # 修改对象的属性
print(id(obj)) # 内存地址不变,属性修改了
140707738784160 # 第一次打印地址
140707738784160 # 第二次打印地址,未变化
可变对象包括列表、字典、集合以及自定义的类对象等。这些对象可以在原地修改,而不会改变对象本身的内存地址。这在处理大量数据时非常有用,因为可以避免频繁创建新对象的开销。
总结
在Python中,理解可变对象和不可变对象的区别,对于编写高效且正确的代码非常重要。两者各有优劣,取决于你想要的行为和性能。
-
不可变对象(如整数、字符串、元组等)由于其内容不能被修改,因此具有较高的安全性,尤其在并发编程中,不可变对象可以避免数据竞争和副作用。 -
可变对象(如列表、字典、集合等)则提供了更大的灵活性,允许你在不创建新对象的情况下直接修改数据,因此在需要频繁修改数据的情况下,它们往往更高效。
写作不易,欢迎关注
Previous Post
Python编程技巧:Monkey Patching实例分析与潜在风险评论已关闭。