在上周的Python科学计算课上,老师讲到了Python序列的浅拷贝以及深拷贝方面的知识,个人觉得说得比较言简意赅了,对于我这个刚入Python的新手来说,也基本可以避免今后变量的赋值使用错乱的问题。

这里我们简单的将Python中的标准数据类型分为两类:

  • 不可变数据类型:int、float、string、boolean
  • 可变(组合)数据类型:列表(list)、字典(dict)、集合(set)

先举几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = 1 # a为上述定义的不可变数据类型
b = a
print('b={}'.format(b)) # b = 1
--------
b = 2
print('a = {},b = {}'.format(a,b)) # a = 1,b = 2

========

c = [1,2,3] # b为上述定义的组合数据类型
d = c
print('c = {},d = {}'.format(c,d)) # c = [1, 2, 3],d = [1, 2, 3]
d.append(4)
print('c = {},d = {}'.format(c,d)) # c = [1, 2, 3, 4],d = [1, 2, 3, 4]

从上述的例子当中看出,在不可变数据类型中,所定义的变量的值在后来改变(这里是b),并不会引起原来赋给它值的那个量的改变(这里是a);而在组合数据类型中就发生了改变,我们只是将d的值进行了改变,并没有直接改变c的值,最后c的值却也发生了变化。

这里,基本数据变量的赋值其实就是深拷贝;组合数据类型的赋值就是起了一个别名。

这里先做出组合数据类型中赋值、浅拷贝、深拷贝三种的区别:

  • 直接赋值:其实就是对象的引用(即给对象起一个别名)。

  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。

  • 深拷贝(deepcopy):copy模块的deepcopy方法,完全拷贝了父对象及其子对象。

    关于内部子对象的概念,下方会再解释。

接下来我们再看一组图(上课ppt图片):

赋值引用

这里的a = {1:[1,2,3]}字典类型。b = a : 赋值引用,a 和 b 都指向同一个对象。可以看出,a,b此刻都指向同一个对象,所以改变b的内容,就是在改变a,b同时所指向的对象的内容,可以理解成b就是a的一个别名。

浅拷贝

这里 a = {1:[1,2,3]} , b = a.copy(),这里就是一种浅拷贝的方式。可以看出a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是引用)。所以在这里L,M就是对象当中的一个子对象([1,2,3])便是这里的子对象。

举个上述的例子:

1
2
3
4
5
6
7
8
9
10
import copy
a = {1:[1,2,3],'北京':'天安门'}
b = copy.copy(a) # b = {1:[1,2,3],'北京':'天安门'}
b[1].append(4)
b['上海'] = '东方明珠'
b['北京'] = '鸟巢'
print('输出:a = {},b = {}'.format(a,b))

-----
输出:a = {1: [1, 2, 3, 4], '北京': '天安门'},b = {1: [1, 2, 3, 4], '北京': '鸟巢', '上海': '东方明珠'}

b = copy.copy(a) 使得b为单独一个对象,但是它和a的子对象指向统一对象。这里的子对象就是[1,2,3](列表子对象)。故当改变b中1键对中的值[1,2,3]时,a也会改变(统一子对象)。但向b中添加值时,便不会对a造成影响,因为这是b自身的对象所拥有的值(和a没有关系)。

那么如何拷贝一个a,但对这个拷贝的对象任意操作时,不会对a产生任何的影响呢?答:采用深拷贝。

如图:

深拷贝

从图中可以清楚的看出:深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象

print( 'a = ', a )
print( 'b = ', b )
print( 'c = ', c )
print( 'd = ', d )

--------
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5] # 给a起了一个别名b,本质相同,故b和a的变化相同
c = [1, 2, 3, 4, ['a', 'b', 'c']] # c中子对象发生了变化 -->浅拷贝
d = [1, 2, 3, 4, ['a', 'b']] # a的改变和d无关 -->深拷贝

总结

对于组合数据类型:

  • 直接赋值:其实就是对象的引用(别名)

  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象

  • 深拷贝(deepcopy):copy模块的deepcopy方法,完全拷贝了父对象及其子对象。


Comment