目录
- 一、必备知识
- 二、基本概念
- 三、列表,元组,集合,字符串,字典浅拷贝
- 3.1 列表
- 3.2 元组
- 3.3 集合
- 3.4 字符串
- 3.5 字典
- 3.6 特别注意
- 可视化展示
- 浅拷贝总结
- 四、列表,元组,集合,字符串,字典深拷贝
- 可视化展示
- 结论
一、必备知识
- 万物皆对象:在学习python的深浅拷贝之前我们必须要知道一个事情,就是python对象的引用,在python里面,万物皆对象,万物皆对象,万物皆对象,不管什么数据类型都是对象。我们定义一个变量并给这个变量赋值的时候赋的并不是这个对象值,而是这个对象引用,并不是一直值,如
a = 1
,这个时候并不是把1
这个值赋给了a
,而是把1
这个对象的引用赋值给了a
。 - 可变序列和不可变序列:可变序列就是可以直接对这个序列直接在原地址上进行数据修改,如果是不可变序列进行数据修改则会创建一个新的对象,让我这个变量重新指向新的对象。关于这部分不不懂的小伙伴可以参考下这篇博文:Python的可变类型与不可变类型,如果看完还是有不明白的地方可以评论区留言。
二、基本概念
-
浅拷贝:浅拷贝会创建一个新的对象,但这个新对象的内可能是院对象的引用或者复制(视具体数据类型而言)。具体来说,如果原对象的元素是可变的(比如列表,字典,集合),浅拷贝会复制这些元素的引用,而不是元素本身。这意味着新对象和原对象会共享这些可变元素。如果对这些共享的元素进行修改,会影响到原对象。如果原对象的元素是不可变的(比如元组或字符串),浅拷贝则会复制这些元素的值,因为它们是不可变的,不会影响到原对象。浅拷贝通常有三种方式:自身构造器,切片和copy.copy()函数,不同数据类型(列表,元组,字典,集合 ,字符串)的浅拷贝会有差异,下面会逐一介绍。
-
深拷贝:重新分配一块新的内存,创建一个新的对象,并将原对象中的元素以递归的方式通过创建新的子对象拷贝到新的对象中。新的对象和原对象之间没有任何关系。深拷贝使用copy.deepcopy()函数实现
-
python自带数据类型: 列表,元组,字典,集合,字符串
-
必备工具网站:pythontutor
三、列表,元组,集合,字符串,字典浅拷贝
3.1 列表
- 自身构造器:
>>> list1 = [1,2,3,4]
>>> list2 = list(list1)
>>> list2
[1, 2, 3, 4]
>>> id(list1)
140495553055488
>>> id(list2)
140495553080256
>>> list1==list2
True
- 切片
>>> list1 = [1,2,3,4]
>>> list2 = list1[:]
>>> list2
[1, 2, 3, 4]
>>> list1 is list2
False
>>> list1 == list2
True
- copy()函数
>>> import copy
>>> list1 = [1,2,3,4]
>>> list2 = copy.copy(list1)
>>> list1
[1, 2, 3, 4]
>>> list2
[1, 2, 3, 4]
>>> list1 is list2
False
>>> list1 == list2
True
注:python中的可变序列有自己的copy()方法,即对于列表和字典这种的对象可以使用list.copy()或者dict.copy()跟copy.copy()函数是等价的。
- 总结:python列表可以使用三种方式进行浅拷贝:自身构造器,切片,copy()函数。浅拷贝之后两个变量的地址不一样,但是数值是一样的,
3.2 元组
- 自身构造器
>>> a = (1,2,3,4)
>>> b = tuple(a)
>>> a
(1, 2, 3, 4)
>>> b
(1, 2, 3, 4)
>>> a is b
True
>>> a == b
True
- 切片
>>> a = (1,2,3,4)
>>> b = a[:]
>>> a is b
True
>>> a == b
True
>>> a
(1, 2, 3, 4)
>>> b
(1, 2, 3, 4)
- copy函数
>>> import copy
>>> a = (1,2,3,4)
>>> b = copy.copy(a)
>>> a
(1, 2, 3, 4)
>>> b
(1, 2, 3, 4)
>>> a is b
True
>>> a == b
True
3.3 集合
- 自身构造器
>>> a = {1,2,3,4}
>>> b = set(a)
>>> a
{1, 2, 3, 4}
>>> b
{1, 2, 3, 4}
>>> a is b
False
>>> a == b
True
- copy函数()
>>> import copy
>>> a = {1,2,3,4}
>>> b = copy.copy(a)
>>> a
{1, 2, 3, 4}
>>> b
{1, 2, 3, 4}
>>> a is b
False
>>> a ==b
True
3.4 字符串
- 自身构造器
>>> a = "1234"
>>> b = str(a)
>>> a
'1234'
>>> b
'1234'
>>> a is b
True
>>> a == b
True
- 切片
>>> a = "1234"
>>> b = a[:]
>>> a
'1234'
>>> b
'1234'
>>> a is b
True
>>> a == b
True
- copy()
>>> import copy
>>> a = "1234"
>>> b = copy.copy(a)
>>> a
'1234'
>>> b
'1234'
>>> a is b
True
>>> a ==b
True
3.5 字典
- 自身构造器
>>> dict1 = {"a":1, 1:2}
>>> dict2 = dict(dict1)
>>> dict1
{'a': 1, 1: 2}
>>> dict2
{'a': 1, 1: 2}
>>> dict1 is dict2
False
>>> dict1 == dict2
True
- copy函数
>>> import copy
>>> dict1 = {"a":1, 1:2}
>>> dict2 = copy.copy(dict1)
>>> dict1
{'a': 1, 1: 2}
>>> dict2
{'a': 1, 1: 2}
>>> dict1 is dict2
False
>>> dict1 == dict2
True
3.6 特别注意
>>> list1 = [[1, 2], (30, 40)]
>>> list2 = list(list1)
>>> list1.append(100)
>>> list1
[[1, 2], (30, 40), 100]
>>> list2
[[1, 2], (30, 40)]
>>> list1[0].append(3)
>>> list1
[[1, 2, 3], (30, 40), 100]
>>> list2
[[1, 2, 3], (30, 40)]
>>> list1[1] += (50,60)
>>> list1
[[1, 2, 3], (30, 40, 50, 60), 100]
>>> list2
[[1, 2, 3], (30, 40)]
- 如果是可变类型,浅拷贝之后一个变量改变不会影响到另一个,但是如果是不可变类型,一个改变了会影响到另一个同时改变。
可视化展示
- 可变类型
list1 = [1,2,[3,4,[5,6]]]
list2 = list(list1)
dict1 = {"a":111, "b":{"c":222, "d":{"e":333}}}
dict2 = dict(dict1)
set1 = {1,2,3,4}
set2 = set(set1)
内存指向:
- 可变和不可变
list1 = [1,2,[3,4, [5,6]]]
list2 = list1
list3 = list(list1)
print(id(list1))
print(id(list2))
print(id(list3))
tuple1 = (1,2,(3,4,(5,6)))
tuple2 = tuple(tuple1)
tuple3 = tuple1 + (7,8)
tuple4 = tuple1
tuple5 = tuple1[1:3]
print(id(tuple1))
print(id(tuple2))
print(id(tuple3))
str1 = "12'34'56''"
str2 = str1[1:5]
str3 = str1
str4 = str1[:]
print(id(str1))
print(id(str2))
print(id(str3))
print(id(str4))
a = 135346347458484
b = int(a)
b = 123
输出:
140007835258880
140007835258880
140007835581440
140007674106368
140007674106368
140007674089280
140007835585072
140007660015344
140007835585072
140007835585072
内存指向
浅拷贝总结
- 可变数据类型的浅拷贝就是为新的变量重新分配一块内存空间,和原来变量的内存不一样,但是变量的值是一样的
- 不可变数据类型不会发生浅拷贝,只是开辟了内存存储原对象的引用,而不是存储原对象的子对象的引用。
- 要与赋值操作区分,赋值只是把原对象的引用赋值给了新的变量,相当于这两个变量指向同一个对象
- 浅拷贝只改改变最外面一层的元素不会影响到原始数据(也就是最外层的数据是复制),改变内层元素则会影响原始数据(内层数据是引用)。
- 可变类型会重新开辟一段内存,最外层元素互不影响,内层元素会指向原始的数据。不可变类型的数据发生完全浅拷贝(这里为了方便介绍理解成浅拷贝,实际上不可变类型不是浅拷贝,数据全部复制)时,新的变量和原始的变量的地址是一模一样的,如果是发生部分数据的复制或者增加数据的时候,则会新开辟一段空间。
- 我们重点关注可变类型,因为不可变类型即使发生了浅拷贝或者赋值对我们后续使用没有任何影响,因为不可变类型数据不能被修改,一旦修改就重新开辟了一个新的内存来存储。
四、列表,元组,集合,字符串,字典深拷贝
可视化展示
- 代码
import copy
list1 = [1,2,[3,4, [5,6]]]
list2 = copy.deepcopy(list1)
print(id(list1))
print(id(list2))
tuple1 = (1,2,(3,4,(5,6)))
tuple2 = copy.deepcopy(tuple1)
print(id(tuple1))
print(id(tuple2))
tuple3 = (1,2,[3,4], {"a":1})
tuple4 = tuple(tuple3)
tuple4[2].append(5)
tuple4[3]["a"] = 2
输出:
140347510384064
140347510378496
140347334201984
140347334201984
- 可视化
结论
-
可变类型深拷贝之后新旧变量地址不一样,不可变类型不变
-
元组类型比较特殊, 元组内部如果嵌套了 可变类型(list, dict)浅拷贝之后, 内部的值修改, 会影响拷贝之后的结果
-
补充:函数传参的时候不可变类型是值传递,可变类型是传引用