跳转至主要内容

Python编程

Python 中的不可变对象 vs 可变对象

Sprite
发表于 2024年10月25日

在Python中,所有的对象都可以归为两类:可变对象不可变对象。这两种类型最大的区别在于,是否可以在对象创建后修改它的值

  • 不可变对象(Immutable Objects):一旦创建,内容就无法被修改。如果要改变值,就必须创建一个新的对象。
  • 可变对象(Mutable Objects):可以在原地修改内容,甚至不需要改变对象的内存地址。

比如我们看下面这个例子:

# 不可变对象例子:整数(int)
x = 10
print(id(x))  # 输出对象的内存地址
x = 20
print(id(x))  # 地址会发生变化,说明x指向了一个新对象

# 运行结果:
# 139752148232144
# 139752148232496

# 可变对象例子:列表(list)
y = [123]
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 = (123)
print(id(t))
t = (456)
print(id(t))  # 重新赋值后,内存地址变化
140707738769088  # 第一次打印地址
140707738794528  # 第二次打印地址,变化了

虽然元组里的元素是不可变的,但要注意,如果元组中包含可变对象(比如列表),这些可变对象本身是可以改变的。

布尔(bool)

布尔类型TrueFalse也是不可变的,和整数一样,它们是固定的,无法更改。

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 = [123]
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 = {123}
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中,理解可变对象不可变对象的区别,对于编写高效且正确的代码非常重要。两者各有优劣,取决于你想要的行为和性能。

  • 不可变对象(如整数、字符串、元组等)由于其内容不能被修改,因此具有较高的安全性,尤其在并发编程中,不可变对象可以避免数据竞争和副作用。
  • 可变对象(如列表、字典、集合等)则提供了更大的灵活性,允许你在不创建新对象的情况下直接修改数据,因此在需要频繁修改数据的情况下,它们往往更高效。

写作不易,欢迎关注

分类:
标签:

评论已关闭。