5.7 引用,可变类型,不可变类型,拷贝
5.7.1 引用
Python中,值是靠引用来传递的。
请看下面两段代码中的b的值:
>>> a = 1
>>> b = a
>>> b
1
>>> a = 2
>>> a
2
>>> b
1
>>> a = [1, 2]
>>> b = a
>>> b
[1, 2]
>>> a.append(3)
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
第一段代码中b的值,没有随a的值改变而改变,但是第二段代码中b的值改变了,区别就在于Python中的数据类型区分为可变类型和不可变类型。
5.7.2 可变类型和不可变类型
可变类型包括:列表、字典、集合。
不可变类型包括:数值,字符串,元组。
不可变的含义是什么?我们可以通过id()函数帮助理解不可变的原理。
id()函数返回一个对象的id号,可以理解为内存中的位置。对于CPython解释器,就代表它在内存中的地址。
对于第一个例子:
>>> a = 1
>>> b = a
>>> id(a), id(b)
(1987931152, 1987931152)
>>> a = 2
>>> id(a), id(b)
(1987931184, 1987931152)
a变量所存储的数值1,在内存中的位置(id值)为1987931152,把a赋值给b时,由于Python传递的是引用,于是这个位置值,也就是引用传递给b。然后把2这个数值赋值给a,注意由于数值是不可变类型,存储数值1的位置里面的值不能改变为数值2,只能另外创建一个数值2,所以这时在内存中有两个数值1和2,1的位置为:1987931152,2的位置为:1987931184,2这个数值的引用被赋给了变量a,而变量b在内存中的位置仍然为数值1的位置,所以a = 2,b = 1。
对于第二个例子:
>>> a = [1, 2]
>>> b = a
>>> id(a), id(b)
(1901464053192, 1901464053192)
>>> a.append(3)
>>> id(a), id(b)
(1901464053192, 1901464053192)
在这里a和b的引用,也就是在内存中的位置值没有改变,因为列表是可变类型,所以允许对列表的修改,并不需要再创建另外一个列表,引用并没有发生变化,所以内存中一直只有一个列表,于是通过变量a对列表的更改,在b中也能看到结果。
明确引用传值以及可变类型和不可变类型,对于理解Python更高级别的内容很有用处。
5.7.3 copy函数
使用copy函数可以创建原对象的副本,将副本赋值给其它变量,id值与原变量不一样,修改可变类型对象的副本不会影响原对象。
>>> e = [1, 2]
>>> id(e)
2874601043016
>>> f = e.copy()
>>> id(f)
2874601051400
>>> e.append(3)
>>> e
[1, 2, 3]
>>> f
[1, 2]
5.7.4 深拷贝 deepcopy
上面的例子中,列表中的元素都是不可变类型,如果其中有可变数据类型,那么普通的copy函数(浅拷贝 shallow copy)并不能使列表中的可变数据类型生成新的副本。例如:
>>> g = [1, 2, [3, 4]]
>>> h = g.copy()
>>> h
[1, 2, [3, 4]]
>>> g[2].append(5)
>>> g
[1, 2, [3, 4, 5]]
>>> h
[1, 2, [3, 4, 5]]
在 Python 中,深拷贝(deep copy)会创建一个全新的对象,并且递归地复制原对象所包含的所有子对象。这意味着原对象和深拷贝后的对象是完全独立的,修改其中一个不会影响另一个。通常用于处理包含嵌套可变数据类型的数据,比如嵌套列表、字典或自定义对象等。
Python 的 copy 模块提供了 deepcopy() 函数来实现深拷贝。
>>> import copy
>>> j = [1, 2, [3, 4]]
>>> k = copy.deepcopy(j)
>>> k
[1, 2, [3, 4]]
>>> j[2].append(5)
>>> j
[1, 2, [3, 4, 5]]
>>> k
[1, 2, [3, 4]]