当重新学习了计算机基础课程《数据结构和算法分析》后再来看这篇自己以前写的博文,发现错误百出。python内置数据类型之所以会有这些特性,归根结底是它采用的是传递内存地址的方式,而不是传递真实值的方式。list使用的是动态顺序存储方式,每一个下标位置存储的是实际值的内存地址,而不是值的本体。
大家都知道,在python中复制一个对象有多种方法,其中常用的是赋值、浅拷贝和深拷贝,这三者之间有哪些区别和哪些坑呢?
首先,定义一下:
赋值: a =1 b =a a赋值给了b
浅拷贝: a = [] b = a.copy()
或者import copy b = copy.copy(a)
深拷贝:import copy a = [] b = copy.deepcopy(a)
通常,我们使用id()方法来查看对象的内存地址。
python对不同类型对象所采取的策略是不一样的,所以我们必须区分不同的类型来进行分析:
(以下全部在python3.5.1环境中测试)
一、数字型
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32 Type "copyright", "credits" or "license()" for more information. >>> a = 1 >>> b = a >>> id(a),id(b) (1630727984, 1630727984) >>> a = 2 >>> id(a),id(b) (1630728016, 1630727984) >>> c = a.copy() Traceback (most recent call last): File "<pyshell#5>", line 1, in <module> c = a.copy() AttributeError: 'int' object has no attribute 'copy' >>> improt copy SyntaxError: invalid syntax >>> import copy >>> c = copy.copy(a) >>> id(a),id(c) (1630728016, 1630728016) >>> a = 3 >>> id(a),id(c) (1630728048, 1630728016) >>> d = copy.deepcopy(a) >>> id (a),id(d) (1630728048, 1630728048) >>> a =4 >>> id (a),id(d) (1630728080, 1630728048) >>>
从测试中我们可以看出,对于数字类型,不管是赋值还是深浅拷贝,一律都是指向同一块地址,而一旦a的值改变了则a重新指向新的内存空间,a,b,c,d成为完全独立的4个变量。一句话概括,对于数据类型随便怎么赋值拷贝,所有变量不会互相影响。(注意,数字类型没有copy()方法。)
二、字符串
>>> import copy >>> a = 'hello' >>> b = a >>> a,b ('hello', 'hello') >>> id(a,b) Traceback (most recent call last): File "<pyshell#21>", line 1, in <module> id(a,b) TypeError: id() takes exactly one argument (2 given) >>> id(a),id(b) (51179736, 51179736) >>> a[0] 'h' >>> a[0] = 'w' Traceback (most recent call last): File "<pyshell#24>", line 1, in <module> a[0] = 'w' TypeError: 'str' object does not support item assignment >>> a = 'world' >>> a,b ('world', 'hello') >>> id(a),id(b) (51179848, 51179736) >>> del a,b >>> a = 'hello' >>> b = a.copy() Traceback (most recent call last): File "<pyshell#30>", line 1, in <module> b = a.copy() AttributeError: 'str' object has no attribute 'copy' >>> b = copy.copy(a) >>> a,b ('hello', 'hello') >>> id(a),id(b) (51179736, 51179736) >>> a = 'world' >>> a,b ('world', 'hello') >>> id(a),id(b) (51179848, 51179736) >>> del a,b >>> a = 'hello' >>> b = copy.deepcopy(a) >>> a,b ('hello', 'hello') >>> id(a),id(b) (51179736, 51179736) >>> a = 'world' >>> a,b ('world', 'hello') >>> id(a),id(b) (51179848, 51179736)
字符串
从测试中我们可以看出,字符串类型和数字类型一样,不管是赋值还是深浅拷贝,一律都是指向同一块地址,而一旦a的值改变了则a重新指向新的内存空间地址。一句话概括,对于字符串类型随便怎么赋值拷贝,所有变量不会互相影响。(注意,字符串类型同样没有copy()方法。)
>>> a = 'hello' >>> b = a[:] >>> a,b ('hello', 'hello') >>> id(a),id(b) (51179792, 51179792) >>> a = 'world' >>> a,b ('world', 'hello') >>> id(a),id(b) (51179904, 51179792)
字符串的切片赋值
同样的,字符串的切片赋值和上面也是一样的效果。
三、列表
>>> a = [1,2,3,[1,2,3]] >>> b = a >>> a [1, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> a[0]=4 >>> a [4, 2, 3, [1, 2, 3]] >>> b [4, 2, 3, [1, 2, 3]] >>> id(a),id(b) (51001160, 51001160) >>> id(a[3]),id(b[3]) (50931208, 50931208) >>> a[3][0]=5 >>> a [4, 2, 3, [5, 2, 3]] >>> b [4, 2, 3, [5, 2, 3]] >>>
赋值
可见,对于列表,赋值其实也是将新的变量指向同样的地址,a的变化会影响到b,b就相当于a的一个符号链接,两者基本等同。
>>> a = [1,2,3,[1,2,3]] >>> b = a.copy() >>> a [1, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> id(a),id(b) (51006152, 50932488) >>> >>> a[0]=4 >>> a [4, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> id(a),id(b) (51006152, 50932488) >>> a[3][0]=5 >>> a [4, 2, 3, [5, 2, 3]] >>> b [1, 2, 3, [5, 2, 3]] >>> id(a[3]),id(b[3]) (3160392, 3160392)
列表的copy()方法
对于列表的copy()方法,从一开始,b就有了和a不同的内存地址,因此对于a的最顶层次的元素的修改,影响不到b。但是!下一层次的修改却在b中得到了反映。这说明copy()方法是一个浅拷贝。
>>> import copy >>> a = [1,2,3,[1,2,3]] >>> b = copy.copy(a) >>> a [1, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> id(a),id(b) (51647752, 51647944) >>> a[0] = 4 >>> a [4, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> a[3][0] = 5 >>> a [4, 2, 3, [5, 2, 3]] >>> b [1, 2, 3, [5, 2, 3]] >>> id(a[3]),id(b[3]) (51647688, 51647688)
copy模块的copy()方法
从例子中可以看出,copy模块的copy()方法可上面的copy方法一样,都是浅拷贝。
>>> a = [1,2,3,[1,2,3]] >>> b = copy.deepcopy(a) >>> a [1, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> id(a),id(b) (51647880, 51647752) >>> a[0] = 4 >>> a [4, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> a[3][0] = 5 >>> a [4, 2, 3, [5, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> id(a[3]),id(b[3]) (51542856, 51647688)
deepcopy()方法
从例子中可以看出,copy模块的deepcopy()方法可以实现深层次的复制,达到完整复制出一份互不干扰的拷贝
>>> a = [1,2,3,[1,2,3]] >>> b = a[:] >>> a [1, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> id(a),id(b) (51647688, 51647368) >>> a[0] = 4 >>> a [4, 2, 3, [1, 2, 3]] >>> b [1, 2, 3, [1, 2, 3]] >>> a[3][0] = 5 >>> a [4, 2, 3, [5, 2, 3]] >>> b [1, 2, 3, [5, 2, 3]] >>> id(a[3]),id(b[3]) (51647752, 51647752)
切片方法
大家知道,字符串、元组和列表有一种切片复制的方法,即b = a[:]。从上面的代码中,我们可以发现,切片其实也是一种浅拷贝。(注意:字典没有切片功能)
四、字典
>>> a = {'k1':1,'k2':2,'k3':{'m1':'a','m2':'b'}} >>> b = a >>> a {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> id(a),id(b) (51561096, 51561096) >>> a['k1'] = 2 >>> a {'k1': 2, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 2, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> a['k3']['m1'] = 'c' >>> a {'k1': 2, 'k3': {'m1': 'c', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 2, 'k3': {'m1': 'c', 'm2': 'b'}, 'k2': 2} >>> id(a['k3']['m1']),id(b['k3']['m1']) (9525768, 9525768)
赋值
从例子中可以见到,赋值的时候,两个字典指向同一个地址,b就是a的一个软链接,相当于别名。
>>> a = {'k1':1,'k2':2,'k3':{'m1':'a','m2':'b'}} >>> b = a.copy() >>> a {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> id(a),id(b) (50977032, 51561096) >>> a['k1'] = 2 >>> a {'k1': 2, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> a['k3']['m1'] = 'c' >>> a {'k1': 2, 'k3': {'m1': 'c', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'c', 'm2': 'b'}, 'k2': 2} >>> id(a['k3']['m1']),id(b['k3']['m1']) (9525768, 9525768)
copy方法
从例子中可以看到,字典的copy方法开辟了一块新的内存空间,但是这也是浅拷贝,只有最顶层的元素获得了独立,以下层次的元素依然指向同一个地址。
>>> a = {'k1':1,'k2':2,'k3':{'m1':'a','m2':'b'}} >>> import copy >>> b = copy.copy(a) >>> a {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> id(a),id(b) (51583560, 51583752) >>> a['k1'] = 2 >>> a {'k1': 2, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> a['k3']['m1'] = 'c' >>> a {'k1': 2, 'k3': {'m1': 'c', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'c', 'm2': 'b'}, 'k2': 2} >>> id(a['k3']['m1']),id(b['k3']['m1']) (9525768, 9525768)
copy模块的copy方法
从例子看出,和上面的一样,这也是个浅拷贝。
>>> import copy >>> a = {'k1':1,'k2':2,'k3':{'m1':'a','m2':'b'}} >>> b = copy.deepcopy(a) >>> a {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> id(a),id(b) (51561096, 51561160) >>> a['k1'] = 2 >>> a {'k1': 2, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> a['k3']['m1'] = 'c' >>> a {'k1': 2, 'k3': {'m1': 'c', 'm2': 'b'}, 'k2': 2} >>> b {'k1': 1, 'k3': {'m1': 'a', 'm2': 'b'}, 'k2': 2} >>> id(a['k3']['m1']),id(b['k3']['m1']) (9787912, 10249080)
deepcopy方法
从例子中可以看出,deepcopy方法完全复制了一份独立的拷贝。
总结:
1.对于数值和字符串类型,属于轻量级对象,python采用的是‘硬’拷贝的方法,每个变量都成为独立的个体。
2.对于列表和字典,为了节省内存空间,python通常采用浅拷贝的方法,只对最顶层的元素进行复制,而以下层次的内容依然指向同一块地址。只有采用深拷贝的方法才能复制出一份独立的拷贝。列表和字典基本是一样的策略,除了字典没有切片功能。
3.浅拷贝只复制列表和字典的最顶层的元素,对于下级的元素依然指向同样的地址,也就是说有关联性。而深拷贝则是完全复制了一份独立的拷贝。
五、使用建议
1. 对于数值、字符串,直接赋值就好,每一个变量都是独立的个体,不用考虑深浅拷贝的问题。
2.对于列表和字典,如果你想完全复制那么只能用deepcopy()方法;如果浅拷贝就行,那可以用copy()方法。但是,不要赋值,因为这实际没什么意义,只是给变量取了个别名而已。