1. 介绍python中的可变类型与不可变类型:
2. 介绍等号赋值、copy、deepcopy的区别:
建议读者首先明白python中变量的本质、可变对象类型与不可变对象类型的区别,之后对于深浅拷贝会容易理解。
1. 等号"="
“=”的作用是引用原对象的在内存中的地址,让新对象指向这个地址。
1.1 不可变对象
对于不可变对象,其值本身不允许被修改,数值的修改实际上是让变量指向了一个新的对象(新创建的对象)。
图1. 不可变对象类型"="拷贝
1 # 1.使用=复制不可变对象的值,以及复制以后修改其值后的变化。
2 val1 = 1000
3 val2 = val1
4 print("val1 is :{0},val2 is :{1}".format(val1,val2))
5 # val1 is :1000,val2 is :1000
6 print(id(val1),id(val2))
7 # 139692380387760 139692380387760
8
9 # 修改val1的值,因为val1是不可变类型,修改其值,会重新给新值分配内存,然后指向他。
10 val1 += 1
11 print(val1,id(val1),val2,id(val2))
12 # 1001 139692380387216 1000 139692380387760
1.2 可变对象类型
对于可变对象类型,"="会引用对象地址,对其进行修改,只会改变对象值的内容,并不修改该对象(不创建新对象,不更改指针指向的地址)。
Tips:
list对象的地址和list[0]对象的地址是不同的。可以理解为list这个组合对象中插入的是其中各个对象的地址的引用。
图2. list对象
1 #1.使用=复制可变对象的值,以及复制以后修改其值后的变化。
2 ls1 =[1,2,3,4]
3 ls2 = ls1
4 print(id(ls1),id(ls2))
5 # 140520429283400 140520429283400
6 # 直接使用=复制变量,内存地址一样,值也一样。
7 print(ls1,ls2)
8 #[1, 2, 3, 4] [1, 2, 3, 4]
9 #直接使用=复制变量,内存地址一样,值也一样。
10 #这时候修改可变对的值,因为其值可变,所以只需要在原内存地址上修改即可。
11 ls1.append(5)
12 print(id(ls1),id(ls2))
13 # 140520429283400 140520429283400
14 #可变对象修改其值,内存引用不变
15 print(ls1,ls2)
16 #[1, 2, 3, 4, 5] [1, 2, 3, 4, 5] 因为两个变量的内存指向一样,所以值也一样。
17
18 print(id(ls1), id(ls2))
19 print(id(ls1[0]), id(ls2[0]))
20 print(id(ls1[1]), id(ls2[1]))
21 print(id(ls1[2]), id(ls2[2]))
22 # 140520429283400 140520429283400
23 # 94472536902176 94472536902176
24 # 94472536902208 94472536902208
25 # 94472536902240 94472536902240
2. copy
2.1 不可变对象类型
效果同"="中的不可变对象
2.2 可变对象类型
对于copy一个可变对象类型的对象,会重新创建一个新的对象,如果原对象是一个组合对象(比如list, 类实例等),会将原组合对象中的各个对象的引用插入到新创建的对象中。
注意,copy只是将其第一层组成对象的内存地址引用过来,并不迭代的引用组成对象的组成对象的地址!所以,如果其组成对象是可变类型,虽然引用地址不变,但其内容可能会变化。详见下面两个例子:
例1.
1 import copy
2 ls1 =[1,2,3,4]
3 ls2 = copy.copy(ls1)
4 print(id(ls1), id(ls2))
5 # 140616587448072 140616587497224
6
7 ls1.append(5)
8 print(ls1,ls2)
9 #[1, 2, 3, 4, 5] [1, 2, 3, 4]
10
11 print(id(ls1), id(ls2))
12 print(id(ls1[0]), id(ls2[0]))
13 # 140616587448072 140616587497224
14 # 94889387870752 94889387870752
例2.
1 origin = [1, 2, [3, 4]]
2 cop1 = copy.copy(origin)
3 origin[2][0] = "hey!" #修改数据源的值
4 print(cop1)
5 #[1, 2, ['hey!', 4]]
对于例2的解释
图3. origin = [1, 2, [3, 4]]的内部结构 图4. copy的实现
可以看出copy只在新对象中插入了第一层的地址引用. 其中只有有橘色框的对象是新建的对象。
那么如果想要拷贝前后两个对象完全互相独立,互不影响要怎样做呢?用deepcopy,递归地将组合对象内部的对象进行深层引用。
3. deepcopy
copy与deepcopy的区别:
The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):
- A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
- A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
1 origin = [1, 2, [3, 4]]
2 cop2 = copy.deepcopy(origin)
3 origin[2][0] = "hey!" #修改数据源的值
4 print(cop2) # [1, 2, [3, 4]]
对比2.2的例2会发现deepcopy递归的将组合对象的每一层的对象都进行了复制。因此,original的对象与deep copy之后的对象是内存地址完全不同的,完全独立的。
图3. origin = [1, 2, [3, 4]]的内部结构 图4. copy的实现 图5. deepcopy与copy实现的对比
其中橘色是deepcopy实现,不仅新建了第一层addr9处的对象,也递归地新建了addr10处的对象,并将addr10引用插入到addr9处的对象中;递归到指向不可变类型的对象为止;因而原对象与新对象是完全独立,互不影响的。
其中绿色是copy实现,仅新建了第一层addr8处的对象,直接将addr5处对象的引用插入到addr8中,并没有为其重新开辟空间,新建对象,因而,原对象中更深层次中内容的变换,会直接影响新对象内容,二者并非完全独立。
所以,慎用copy~