一、赋值--"旧瓶装旧酒"
在python中,对象的赋值就是简单的对象引用
, 这点和C++等语言不同.如:
In[2]: a = [1, 2, 'hello', ['python', 'C++']] In[3]: b = a In[4]: a is b Out[4]: True In[5]: b is a Out[5]: True In[6]: id(a) Out[6]: 139705399858952 In[7]: id(b) Out[7]: 139705399858952 # 总结: 1.通过 is 判断它们的内存地址相同; 2.通过 id() 来查看两者的内存地址也相同. In [1]: a = 3 In [2]: b = 3 In [3]: a is b Out[3]: True In [4]: id(a) Out[4]: 10919488 In [5]: id(b) Out[5]: 10919488 # 通过这个例子,很容易说明: 1. python 中 不可变类型 在内存中只有一份, 不论如何嵌套,其id()值相同.
在上述情况下,a
和b
是一样的,它们指向同一片内存地址,b不过是a的别名,是引用.
赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,只是复制了对象的引用. 也就是说 除了 b 这个名字之外, 没有其它的内存开销. 修改了 a 或 b , 另外一个 b 或 a 同样跟着受影响.
python的赋值操作还有一个通俗的理解: 先在内存中创建等号右边的对象, 然后把等号左边的变量作为标签贴在右边对象上,是为引用.
二、浅拷贝(shallow copy)--"新瓶装旧酒"
浅拷贝会创建新对象, 其对象非原来对象的引用, 而是原来对象内第一层对象的引用.
通俗理解:拷贝了引用,并没有拷贝内容;产生了新对象,但是里面的内容还是同一份
浅拷贝 三种形式: 切片操作 工厂函数 copy模块中的copy函数. 比如上述列表a;
-
切片操作: c = a[:]
-
工程函数: c = list(a)
-
copy函数: c = copy.copy(a)
In[8]: c = a[:] In[9]: c is a Out[9]: False In[10]: id(a) Out[10]: 139705399858952 In[11]: id(c) Out[11]: 139705390811656 In[12]: [id(x) for x in a] Out[12]: [10919424, 10919456, 139705494889056, 139705399859272] In[13]: [id(x) for x in c] Out[13]: [10919424, 10919456, 139705494889056, 139705399859272] # 总结: 1. b 不在(is)是 a ,也不指向(id())同一个内存空间; 2. 但是 [id(x) for x in a] 和 [id(x) for x in c] 结果相同. 说明: a 和 c 内部的元素对象指向的是同一片内存地址. In[14]: id(a[3][0]) Out[14]: 139705495480280 In[15]: id(c[3][0]) Out[15]: 139705495480280 In[16]: a[3][0] Out[16]: 'python' In[17]: a[3].append('java') In[18]: b Out[18]: [1, 2, 'hello', ['python', 'C++', 'java']] # 总结: 1. 关于浅copy的理解,个人以为用内层和外层来区分更容易理解.通俗来说,浅拷贝就是只修改了最外层的引用, 使得元素最外层的地址变化,但是对原来元素的内层地址和引用均未做修改. 2. 把这里最外层列表中的每一个元素(包括列表元素)都看作一个'坑', 每个'坑'再指向一个具体的 对象. 这就是'引用'. In[19]: a[1] = 10 In[20]: a Out[20]: [1, 10, 'hello', ['python', 'C++', 'java']] In[21]: c Out[21]: [1, 2, 'hello', ['python', 'C++', 'java']] In[22]: b Out[22]: [1, 10, 'hello', ['python', 'C++', 'java']] # 事实上,这里就比较绕; 思路还是要从 是否修改引用 来思考. 1. python中一直都是先在内存中有对象,然后再对对象有引用. 2. [17]修改的是 嵌套列表内部的元素,并未对最里层列表本身的 内存地址 产生影响, 所以 a 和 c 指向的内层列表仍然是同一个对象,所以其中一个修改,两者同时发生改变. 3. [19]修改的是 不可变类型, 相当于 指向了新的引用;所以 a 和 c 不同.
三、深拷贝(deep copy)--"新瓶装新酒"
深拷贝 只有一种形式 , copy模块中的 deepcopy()
函数
深拷贝拷贝了对象的所有元素,包括多层嵌套的元素. 因此,它的时间和空间开销要高.
深拷贝拷贝出来的对象根本就是一个全新的对象,不再于原来的对象有任何的关联.
深拷贝类似没有 出口的递归拷贝
四、拷贝注意点
对于非容器类型, 如数字 , 字符 , 以及其它的"原子"类型, 没有拷贝一说, 产生的都是原对象的引用.
如果元组变量值包含原子类型对象, 即使采用了深拷贝,也只能得到浅拷贝.