August Rush

一个还在努力成长的小火汁!

游龙当归海,海不迎我自来也。

We create our own demons.

You can reach me at augustrush0923@gmail.com
Python赋值、浅拷贝和深拷贝
发布:2021年05月27日 | 作者:augustrush | 阅读量: 1170

来看一张简单易懂的图理解一下python的引用语义和C语言值语义在内存中的存储情况,左右两个图,分别表示了python中变量存储与C语言中变量存储区别:


赋值

在Python中,对象的赋值就是简单的对象引用。如下所示

a = [1,2,"hello", ['python', 'Golang']]
b = a
# True
print(id(a))
# 140524502784992
print(id(b))
# 140524502784992

在上述情况下,a和b是一样的,他们指向同一片内存,b不过是a的别名,是引用。

可以使用b is a去判断,返回True,表明他们地址相同,内容相同,也可以使用id()函数来查看两个列表的地址是否相同。

赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,它只是复制了对象的引用。也就是说除了b这个名字之外,没有其他的内存开销。修改了a,也就影响了b,同理,修改了b,也就影响了a。

简单的比喻一下,我们出去吃饭,a和b就像是同桌吃饭的两个人(赋值操作),两个人公用一张桌子(共享同一个内存地址),只要桌子不变(内存地址不变),桌子上的菜发生了变化两个人是共同感受的。


浅拷贝(shallow copy)

浅拷贝会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。

浅拷贝有三种创建形式:

  • 切片操作;

  • 工厂函数;

  • copy模块中的copy函数。

例如:

# 比如上述的列表a;
# 切片操作:
b = a[:]
# OR
b = [x for x in a]

# 工厂函数
b = list(a)

# copy函数
b = copy.copy(a)

浅拷贝产生的列表b不再是列表a了,使用is判断可以发现他们不是同一个对象,使用id查看,他们也不指向同一片内存空间。但是当使用id(x) for x in aid(x) fro x in b来查看a和b中元素的地址时,可以看到二者包含的元素的地址是相同的。

在这种情况下,列表a和b是不同的对象,修改列表b理论上不会影响到列表a。

但是要注意的是,浅拷贝之所以称之为浅拷贝,是它仅仅只拷贝了一层,在列表a中有一个嵌套的list,如果我们修改了它,情况就不一样了。

比如:a[3].append('java')。查看列表b,会发现列表b也发生了变化,这是因为,我们修改了嵌套的list,修改外层元素,会修改它的引用,让它们指向别的位置,修改嵌套列表中的元素,列表的地址并未发送变化,指向的都是同一个位置。

这种情况发生在字典套字典、列表套字典、字典套列表、列表套列表,以及各种复杂数据结构的嵌套中,所有当数据类型很复杂的时候,用copy去进行浅拷贝就要非常小心!


深拷贝(deep copy)

深拷贝只有一种形式,copy模块中的deepcopy()函数。

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

同样的对列表a,如果使用b = copy.deepcopy(a),再修改列表b将不会影响到列表a,即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。


注意点

对于非容器类型,如数字、字符,以及其他的“原子”类型,没有拷贝一说,产生的都是原对象的引用。

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



  • 标签云

  • 支付宝扫码支持一下

  • 微信扫码支持一下



基于Nginx+Supervisord+uWSGI+Django1.11.1+Python3.6.5构建

京ICP备20007446号-1 & 豫公网安备 41100202000460号

网站地图 & RSS | Feed