刚接触python的时候,就听说过浅拷贝(shallow copy)与深拷贝(deep copy)的不同之处,最开始也只能避免“直接赋值”这种“呆瓜”且极易出错的编程行为,在粗浅地使用python编写数量不多的代码的时,函数量较小,一个函数的输出并没有什么机会当做另外一个函数的输入,直接使用自带的浅拷贝函数—object.copy(),出错了就直接换成copy模块中的copy.deepcopy()便也解决了问题,但是最近在学习使用群智能优化算法,大量的函数套用,由于尚未养成良好的编程习惯,有时使用numpy模块有时使用列表(list),逼得我不得不深入研究一下浅拷贝、深拷贝以及numpy中的copy的区别,为了记忆更加牢固便写一篇博客
下面放一下个人学习的连接
Python 直接赋值、浅拷贝、深拷贝——菜鸟教程
| 名称 | 实质 |
|---|---|
| 直接赋值 | 其实就是对象的引用(别名) |
| 浅拷贝(copy) | 拷贝父对象,不会拷贝对象的内部的子对象 |
| 深拷贝(deepcopy) | copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象 |
字典浅拷贝实例
>>>a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
深拷贝
>>>import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})
解析如下:
核心就是指针!!!这种被“刻意隐藏”的指针,是初学者的一个大坑,如果没有学过C/C++更加难以理解指针的含义
简单说一下,指针可以理解为是一个标签,当执行a = 1的时候,1就被贴上a这个标签,通过使用a就可以访问1,当执行b = a的时候,也就是直接赋值法,此时1又被贴上了b这个标签,用b也可以访问1,就是下面这个效果:
>>> a = 1 >>> b = a >>> a,b (1, 1)
但是当再次使用a = 3的时候,1上a这个标签就被“撕掉”了,3被贴上了a的标签
>>> a = 3 >>> a,b (3, 1)
了解过之后,可以试试list结构:
>>> a = [1,2,[2,3]] >>> a[:] [1, 2, [2, 3]] >>> b = a[:] >>> b [1, 2, [2, 3]] >>> a[2].append(5) >>> a [1, 2, [2, 3, 5]] >>> b [1, 2, [2, 3, 5]] >>> id(a) 1895430236608 >>> id(b) 1895461248448 >>> id(a[2]),id(b[2]) (1895461745920, 1895461745920)
可以看出[:]也可以实现浅拷贝:a与b的地址不同,但是a[2]与b[2]的地址相同
list使用*来建立“类”(类似)array与numpy的array首先是list错误的*使用方式
>>> wrong = [[0]*3]*3 >>> wrong [[0, 0, 0], [0, 0, 0], [0, 0, 0]] >>> wrong[0][0] = 10 >>> wrong [[10, 0, 0], [10, 0, 0], [10, 0, 0]] >>> id(wrong[0]),id(wrong[1]),id(wrong[2]) (1895461748096, 1895461748096, 1895461748096)
发现每一行的指针都是指向同一个list对象,*相当于一个直接赋值的操作
正确的方式,每一行的指针都是指向不同的list对象
>>> right = [[0]*3 for _ in range(3)] >>> right [[0, 0, 0], [0, 0, 0], [0, 0, 0]] >>> right[0][0] = 5 >>> right [[5, 0, 0], [0, 0, 0], [0, 0, 0]] >>> id(right[0]),id(right[1]),id(right[2]) (1895461459392, 1895461683008, 1895461746752)
关于numpy的array,大家看到下面的代码可能会有些疑惑,因为array里面貌似也是一个list的子对象,浅拷贝不应该a,b同一个位置的值都应该改变么,可实际上可以看出并没有产生上述猜想结果。可以把他当做一个真正意义上的矩阵,每一个位置的值都是独立的,并没有list子对象这种存在,具体的原因可能需要去看numpy的实现源码,待读者独立去发掘。
>>> import numpy as np
>>> a = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> b = a.copy()
>>> a
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> b
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> a[0][0] = 100
>>> a
array([[100, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9]])
>>> b
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
最后的最后
看看下面几行代码,为何a,b地址不同,c,d相同呢?这里具体有两个知识点
一是python对于不可变对象与可变对象的存储引用方式的不同,待读者自己去搜索。
二是python对于一定范围内的整数,他们的标签都指向同样一个位置,都是已经预存好的位置。仔细观察c、d、e、f的地址,前面都一致,但是最后四位差距并不是很大:0——3792;1——3824;2——3856,相差32为内存位置,单位我的猜测是Byte,也就是32*8=256bits所能表示的数字,都是存在固定的位置上的,当然这都是我的猜测,因为不同有含义的内存块之间可能存在内存间隔,所以这个256也许是128,或者64也不一定呢。
>>> a = [1,2] >>> b = [1,2] >>> id(a),id(b) (2276089527168, 2274186029824) >>> c = 1 >>> d = 1 >>> id(c),id(d) (2276049643824, 2276049643824) >>> e = 2 >>> id(e) 2276049643856 >>> f = 0 >>> id(f) 2276049643792



