作者:Seaton
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。
Python中数据类型的可变性,等号赋值与深浅拷贝的区别
在Python编程语言中,数据类型的可变性是一个重要的概念。了解数据类型的可变性对于编写高效和安全的代码至关重要。本博文将深入探讨Python中的可变和不可变数据类型,以及它们在实际编程中的应用。
一、不可变数据类型
不可变数据类型指的是一旦创建,其值就不能更改的数据类型。常见的不可变数据类型包括:
- 整数(Integers): 如 42 或 -5
- 浮点数(Floats): 如 3.14 或 -0.001
- 字符串(Strings): 如 ‘hello’ 或 “Python”
- 元组(Tuples): 如 (1, ‘a’, 3.14)
当我们尝试修改这些类型的对象时,实际上是创建了一个新的对象。
二、可变数据类型
可变数据类型指的是创建后可以修改其值的数据类型。常见的可变数据类型包括:
- 列表(Lists): 如 [1, 2, 3]
- 字典(Dictionaries): 如 {‘key’: ‘value’}
- 集合(Sets): 如 {1, 2, 3}
对这类对象进行操作时,可以直接修改其内容,而不需要创建新的对象。
三、python中数据的等号赋值
在Python中,使用等号(=)赋值时,通常是创建了一个对象的引用,而不是复制对象本身。这意味着,如果你将一个对象赋值给另一个变量,那么这两个变量将会引用内存中的同一个对象。
对于不可变数据类型(如整数、浮点数、字符串、元组等),当你对一个变量赋值时,如果改变了这个变量的值,实际上是创建了一个新的对象,并且让这个变量指向这个新对象。因为不可变对象一旦创建就不能更改,所以只能通过重新赋值来改变变量的内容。
对于可变数据类型(如列表、字典、集合等),当你对一个变量赋值时,如果你改变了这个变量的内容(例如,添加、删除或修改列表中的元素),那么这些修改会直接反映在所有引用这个对象的变量上,因为它们都指向内存中的同一个对象。
如果想要创建一个对象的副本,你可以使用浅拷贝(copy模块的copy()函数)或深拷贝(copy模块的deepcopy()函数)。浅拷贝会创建一个新的容器对象,但是容器内的元素仍然是原对象的引用。深拷贝则会递归地复制对象及其包含的所有对象,创建一个完全独立的副本。
四、示例分析
4.1 等号赋值
等号用来赋值,创建对象的引用,而不是复制对象。
当对不可变数据类型进行操作时,实际上会创建一个新的对象。
a = 5
b = a
a = a + 1
print(a) # 输出 6
print(b) # 输出 5
在上面的例子中,a 和 b 分别指向内存中不同的对象。
当对可变数据类型进行操作时,可以直接修改其内容,而不需要创建新的对象。
a = [1, 2, 3]
b = a
a.append(4)
print(a) # 输出 [1, 2, 3, 4]
print(b) # 输出 [1, 2, 3, 4]
在上面的例子中,a 和 b 指向内存中同一个对象,因此修改 a 也会影响到 b。
特殊情况
虽然不可变数据类型的值本身不能被改变,但是它们可以作为可变数据类型的元素,从而间接地被修改。
a = [1, 2, 3]
b = a
a[0] = 0
print(a) # 输出 [0, 2, 3]
print(b) # 输出 [0, 2, 3]
在上面的例子中,虽然整数 1 是不可变的,但是它作为列表 a 的元素,可以被修改。
4.2 浅拷贝
浅拷贝(copy)复制对象及其包含的元素的引用,不复制嵌套的对象。
import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
a[0] = 0
a[2][0] = 0
print(a) # 输出 [0, 2, [0, 4]]
print(b) # 输出 [1, 2, [0, 4]]
4.3 深拷贝
深拷贝(deepcopy)则递归复制对象,以及它包含的所有层次嵌套的对象和元素。
import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
a[0] = 0
a[2][0] = 0
print(a) # 输出 [0, 2, [0, 4]]
print(b) # 输出 [1, 2, [3, 4]]
在上面的例子中,修改 a
中的列表[3, 4]
的第一个元素不会影响到 b
,因为b
是 a
的深拷贝。
4.4 等号赋值与深浅拷贝的区别
对于不可变数据类型
import copy
a = 1
print(id(a), a) # 1422344808752 1
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a = 5
print(id(a), a) # 1422344808880 5
print(id(b), b) # 1422344808752 1
print(id(c), c) # 1422344808752 1
print(id(d), d) # 1422344808752 1
可以看到,修改了a之后,a的地址变了,说明对于不可变数据类型,修改其值相当于创建了一个新的对象。
- 等号赋值:这里b被赋予与a相同的引用,因此它们指向相同的对象,所以id(b)与id(a)相同。
- 浅拷贝和深拷贝:对于不可变对象而言,浅拷贝和深拷贝并没有实际创建新的对象,它们仍然指向原始对象,因此id©和id(d)与id(a)相同。
因此,对于不可变对象,等号赋值与深浅拷贝并无区别。
而对于可变数据类型
import copy
a = [1, 2, [3, 4]]
print(id(a), a) # 2331941478976 [1, 2, [3, 4]]
print(id(a[0]), a[0]) # 2331933042992 1
print(id(a[2][0]), a[2][0]) # 2331933043056 3
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a[0] = 5
a[2][0] = 6
print(id(a), a) # 2331941478976 [5, 2, [6, 4]]
print(id(b), b) # 2331941478976 [5, 2, [6, 4]]
print(id(c), c) # 2331941530176 [1, 2, [6, 4]]
print(id(d), d) # 2331941530240 [1, 2, [3, 4]]
print(id(a[0]), a[0]) # 2331933043120 5
print(id(b[0]), b[0]) # 2331933043120 5
print(id(c[0]), c[0]) # 2331933042992 1
print(id(d[0]), d[0]) # 2331933042992 1
print(id(a[2][0]), a[2][0]) # 2331933043152 6
print(id(b[2][0]), b[2][0]) # 2331933043152 6
print(id(c[2][0]), c[2][0]) # 2331933043152 6
print(id(d[2][0]), d[2][0]) # 2331933043056 3
可以看到,a的地址没有改变,但值全部改变了。说明修改a的值是在原来地址的基础上修改的,而不是创建新的对象。
- 等号赋值:b的地址和值完全与a相同,因为等号赋值是创建一个引用,b完全随着a的变化而变化;
- 浅拷贝:c的地址与a不同,值部分相同。因为浅拷贝是创建一个新的对象,但新对象中的子嵌套对象仍然是a的引用,所以不会改变;
- 深拷贝:d的地址与a不同,值完全相同。因为深拷贝则创建了一个全新的列表,同时递归地复制了所有子对象,包括嵌套的列表。所以全部都是a原来的值。
五、总结
对于不可变数据类型,等号赋值和深浅拷贝没有任何区别,都是创建一个新的引用。
对于可变数据类型,等号赋值、浅拷贝和深拷贝完全不同,
- 等号赋值创建一个引用,其值完全随着原变量而变;
- 浅拷贝创建一个对象,但内部子对象还是原来对象的引用;
- 深拷贝创建一个对象,且内部子对象也是新的对象。