zoukankan      html  css  js  c++  java
  • python 的内存回收,及深浅Copy详解

    一、python中的变量及引用
    1.1 python中的不可变类型:
    数字(num)字符串(str)元组(tuple)布尔值(bool) 接下来我们讲完后你就懂了为什么它们是不可变对象了。 都知道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 。在函数的命名空间中可以查到这些,详情看我这篇文章

    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 共享引用
    如图:指两个或多个变量指向同一个内存空间 ![](https://images2018.cnblogs.com/blog/1226829/201808/1226829-20180804124229776-277665676.png)

    如果删掉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}
    浅拷贝:{1}
    深拷贝:{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'对应的哪儿就是只拷贝了内存地址,而深拷贝还要去内存地址拷贝内容回来赋值
    原理看到这儿,差不多也懂了,就不罗嗦了!

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

    一个有意思的练习题

    import copy
    a = [1,2,3,[4,5],6]
    b=a
    c=copy.copy(a)
    d=copy.deepcopy(a)
    b.append(10)
    c[3].append(11)
    d[3].append(12)
    

    a,b,c,d分别为什么?
    答案我放评论

  • 相关阅读:
    自学MVC(十二):MVC视频项目小结
    Silverlight MMORPG WebGame游戏设计(一)一个游戏爱好者的webGame之路
    自学MVC(十四):如何在view里用表格展示json数组
    自学MVC(十):自己写的通用的JS分页控件2009年05月27日
    自学MVC(八):在jquery里让DIV随鼠标移动2009年05月15日
    自学MVC(十一):用js把siverlight播放器加到页面里2009年05月30日
    自学MVC(十三):MVC视频项目的小Tipps
    Silverlight MMORPG WebGame游戏设计(二)通讯协议之惑
    安卓客户端连接服务器调用数据库方法
    第一次ADO.Net连接SQLserver测试时出现的问题传智播客的ADO例子
  • 原文地址:https://www.cnblogs.com/shiqi17/p/9417663.html
Copyright © 2011-2022 走看看