网站首页 文章专栏 python高阶教程-对象管理与垃圾回收
本篇内容来自原创小册子《python高阶教程》,点击查看目录。
考虑如下代码,运行后输出为?
a = {1:[1,2,3]} b = a.copy() a[1][0] = 2 print(a) print(b)
输出:
{1:[2,2,3]} {1:[2,2,3]}
为什么b的输出不是{1:[1,2,3]}
呢? 这要从python的对象管理说起。
python的对象是基于引用来管理的,每个对象维护一个引用计数器。
比如如下代码:
var1 = 'A' var2 = var1 var3 = 'B'
运行后在内存中的分配如下:
可以看到var1 和 var2 的内容都是对象'A', 但是两者共享一个对象。
我们可使用copy模块中的函数来复制一个复杂对象,主要分为shallow copy
和deep copy
两类
shallow copy
仅复制第一层,不递归复制。切片、copy
模块的copy
函数
deep copy
对子对象进行递归复制。Copy
模块的deepcopy
函数
上图是不同的函数复制后的效果,可以看到deep copy
函数把字典对象里面嵌套的列表对象也完全复制过来了,但是shallow copy
不复制嵌套对象。
有用的对象叫对象,无用的对象叫垃圾,有垃圾就要有回收机制,在python中垃圾回收是自动进行的。主要有如下规则:
import gc gc.get_threshold()
输出:
(700, 10, 10)
元组表示的含义为:计数器达到700时启动0代对象的回收;每经过10次0代对象回收,额外对1代对象进行一次回收;每经过10次1代对象回收,额外对2代对象进行一次回收。
对于第4条,在python中维护一个计数器,用来统计内存分配与回收的个数,同时用来启动0代垃圾回收。有如下代码:
import gc class A(): pass print(gc.get_count()) a = A() print(gc.get_count()) del a print(gc.get_count())
输出:
(574,8,0) (575,8,0) (574,8,0)
可以看到经历了一次内存分配与删除后,输出元组的第一个数字经历了574-575-574
的变化。不难看出,第二个数字代表距上一次1代垃圾回收已经过去了8个计数器的值,经过上一次2代垃圾回收已经过去了0个计数器的值。
具体的垃圾回收操作是找到引用计数为0的对象并销毁,但是,如果是如下代码呢?
a = [] b = [] a.append(b) b.append(a)
这里a和b循环引用,内存中对象的引用计数永远不为0,该如何进行垃圾回收呢?
标记清除主要有以下规则:
用一个例子来解释
在该例中,共有四个对象,分别是'A', 'B', 'C', 'D', 引用计数都为1, 但是'A'引用了'C',
'B'引用了'D'。
标记清除操作开始。首先,复制对象的引用计数。然后,只要对象间有引用,不管是不是循环引用,被引用对象的计数值都要减1. 如A引用了C,C的引用值减1,B引用了D,D的引用值减1,其余类推。最后,把不为0的对象加入root链表,root列表引用的对象加入root链表,其他加入unreachable链表。在此例中,A首先被加入root链表,被A引用的C也被加入root链表。
标记清除操作结束后,对unreachable链表中的对象进行垃圾回收。