python 的内存回收,及深浅Copy详解

一、python中的变量及引用
1.1 python中的不可变类型:

数字(num)字符串(str)元组(tuple)布尔值(bool<True,False>)
接下来我们讲完后你就懂了为什么它们是不可变对象了。
都知道python中一切都是对象,而变量就是这些对象的引用,什么意思呢
综合表述:
变量是一个系统表的元素,拥有指向对象的连接的空间

对象是被分配的一块内存,存储其所代表的值

引用是自动形成的从变量到对象的指针

特别注意: 类型属于对象,不是变量

>>> c = 17 #1 数字17就是一个对象,实实在在存在计算机内存中
>>> d = c  #2  c 和 d 都是对象17的一个引用,c指向17,d也是
>>> id(c)   #3
1462698960
>>> id(d)   #4
1462698960

在#1 处我们定义了各一个变量c,c指向了17(把17赋值给c),对象17的一个引用c

然后在#2处,又定义了一个变量d ,把c赋值给了d,接着#3、#4查看了c、d的 id 相同,
发现是同一个对象(17),对象17的引用+1

引用:
对象17的引用现在有两个了
变量:
在内部,变量事实上是到对象内存空间的一个指针

1.2 python中内存回收机制

1.2.1 python本身是一门动态语言
与c/c++ /java不同,不需要事先定义变量开辟内存空间,然后给变量赋值,存储到变量的内存空间中。使用结束,当然也不需要你去手动调用析构函数释放内存了。
python会预先申请一部分内存空间,在运行时定义了变量-对象,根据对象确认它的type,将对象放到申请的内存中,python每过一段时间就来检查一次, 当有对象的引用为0时,就回收这块内存,返还回先申请的内存空间,而不是计算机。这样避免了内存碎片过多问题。

1.2.2 怎么减少对象的引用

  1. 将变量引用指向其他对象
>>> c = 17
>>> d = c
>>> id(c)    #1
1462698960
>>> id(d)    #2
1462698960
>>> c = "yue"  #3
>>> id(c)        #4
612496081896
>>> d  #5
17

可以看到#1、#2处c、d都还是对象17的引用,当#3处把变量c 指向新对象字符串"yue" 时,#4处发现变量c指向的对象id变了,的确不是17了,所以对象17的引用 -1 如下图
注意:这儿改变了c的引用,可是#5处d却没有跟着c变,还是对象17

同理当你再把d指向其他对象时,对象17的引用就减为零,当Python来检查时,就会回收这块内存了

2.删除变量(引用)

>>> del d
>>> d
Traceback (most recent call last):
  File "<stdin>", line 1, in <modul
NameError: name 'd' is not defined

不啰嗦,这样对象17就彻底被删除了,上图时对象17只剩下一个变量引用d。
同理对于函数,定义函数时,函数名就是一个引用,当其他地方调用函数时,引用+1,调用结束 -1 。在函数的命名空间中可以查到这些,详情看我这篇文章

扫描二维码关注公众号,回复: 2556888 查看本文章

python的内存回收就到这儿:总结:回收机制为判断对象 引用是否为0,如果为零就回收内存到自己申请的内存空间,不是计算机硬盘。

1.3 再谈不可变类型

通过上面的式子和图理解我们也知道了,当定义变量为数字、字符串、tuple、布尔值时,这些变量所对应的对象在内存空间的值是不可改变了,你重新赋值,也只是把变量引用指向了另一个对象,id变了,本身那个对象是不可变的。

>>> a = (1, 'one')
>>> id(a)
612494666568  #1
>>> a[0] = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a[0]
1
>>> a = (2, 'two')
>>> id(a)
612494666824 #2

#---------------------------------------------
>>> a = 'findxgo'  #3
>>> id(a)
612496082848
>>> a.replace('x','--X--')  #4
'find--X--go'
>>> id(a)   #5
612496082848
>>> a = a.replace('x', '-X-') #6
>>> id(a)  
612496086704

在#3出定义了字符串a,#4处替换x,得到一新字符串,但是原字符串还是#5id没变,当#6把替换的字符串赋值给变量a,a的引用指向了替换后新字符串

二、python中的深浅Copy
2.1 共享引用

如图:指两个或多个变量指向同一个内存空间

如果删掉c后, 不会影响d

拷贝概念的引入就是针对:可变对象的共享引用潜在的副作用而提出的。

2.2 可变对象

2.2.1 指python中,存储在内存可以被修改的对象:列表、字典等
上面说的数字、字符串、元组等不可变类型,在你复制时也就是增加了一个引用,无法去改变内存的值。对对象的其中一个引用变量操作不会影响其他引用。
但是对于列表、字典:

>>> list_1 = [5, 2, 1]
>>> L2 = list_1  #1 将list_1赋值给L2
>>> list_1,L2
([5, 2, 1], [5, 2, 1])
>>> list_1[2] = '01314'     #2 修改list_1 索引2处的值
>>> list_1,L2
([5, 2, '01314'], [5, 2, '01314'])

可以看到#1 上面定义一个列表,赋值给L2后,L2、list_1对应完全一样的值(列表)事实上,他两的确对应着一块内存,你可以自己去查id,是那块内存(列表)的两个引用
当你去在list_1,或者L2进行操作时,改变了对应内存的值,所以#2下面两个值都变了。python中同一块内存(对象)的不同引用改变对象所以引用都会被影响。
同理字典:通过自己哈希表将key计算后得到的内存地址就是存放value的地方,当你用如上同样的方式改变哪儿的值,所有引用都会被影响。

2.3 浅copy

上述的情况如果想避免,有两种方式,原理都一样:copy一份放到另一个内存,变成同样(value)的两个对象,当你修改其中一个时,另一个不会影响。
1、切片复制:完全切片

>>> list_1 = [5, 2, 1]
>>> L2 = list_1[:]     #1 此处完全切片,复制
>>> list_1,L2
([5, 2, 1], [5, 2, 1])
>>> id(list_1)  #2 查看id 
612496051784
>>> id(L2)     #3
612496051720

如上:#1处完全切片也可以L2 = list_1[0: -1]是一样的,#2,#3处可以看见,id不同,就不是同一个对象,只是里面的value相同而已
同理copy模块的copy方法,这是浅拷贝。

2.4 深copy
  1. 深浅拷贝,即可用于序列,也可用于字典
>>> import copy

>>> dict_1 = {'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}

>>> D2 = copy.copy(dict_1)      #浅拷贝:只拷贝顶级的对象,也说:父级对象

>>> D3 = copy.deepcopy(dict_1)  #深拷贝:拷贝所有对象,顶级对象及其嵌套对象。或者说:父级对象及其子对象

>>> print("源:{0: ^18}\n浅拷贝:{1}\n深拷贝:{2}".format(id(dict_1),id(D2),id(D3)))
源:   37811303432
浅拷贝:37813197256
深拷贝:37813160264

2.改变源顶级对象,深浅拷贝不会变

>>> dict_1['copy'] = 'n_copy'
>>> dict_1;D2;D3
{'copy': 'n_copy', 'deepcopy': ['deep', '第二层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}

3.改变源嵌套对象,浅拷贝变了,深拷贝不变

>>> dict_1['deepcopy'][1] = '嵌套层'
>>> dict_1;D2;D3
{'copy': 'n_copy', 'deepcopy': ['deep', '嵌套层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '嵌套层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}

这儿的浅拷贝,只拷贝了父级对象,在'deepcopy'对应的哪儿就是只拷贝了内存地址,而深拷贝还要去内存地址拷贝内容回来赋值
原理看到这儿,差不多也懂了,就不罗嗦了!

三、总结

  • 深浅拷贝都是对源对象的复制,占用不同的内存空间
  • 如果源对象只有一级目录的话,源做任何改动,不影响深浅拷贝对象
  • 如果源对象不止一级目录的话,源做任何改动,都要影响浅拷贝,但不影响深拷贝
  • 序列对象的切片其实是浅拷贝,即只拷贝顶级的对象

猜你喜欢

转载自www.cnblogs.com/shiqi17/p/9417663.html
今日推荐