python:赋值 | 浅拷贝 | 深拷贝

版权声明:博客追求原创,如果有借鉴尽量都在文后注明 https://blog.csdn.net/DefaultTest/article/details/83188047

一、赋值--"旧瓶装旧酒"

在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()值相同.

在上述情况下,ab是一样的,它们指向同一片内存地址,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() 函数

深拷贝拷贝了对象的所有元素,包括多层嵌套的元素. 因此,它的时间和空间开销要高.

深拷贝拷贝出来的对象根本就是一个全新的对象,不再于原来的对象有任何的关联.

深拷贝类似没有 出口的递归拷贝

四、拷贝注意点

对于非容器类型, 如数字 , 字符 , 以及其它的"原子"类型, 没有拷贝一说, 产生的都是原对象的引用.

如果元组变量值包含原子类型对象, 即使采用了深拷贝,也只能得到浅拷贝.

猜你喜欢

转载自blog.csdn.net/DefaultTest/article/details/83188047
今日推荐