python内存管理、引用、深浅拷贝、垃圾回收机制
对象引用
引用计数
变量:通过变量指针引用对象
对象:类型已知,每个对象都包含一个头部信息(头部信息:类型标识赫然引用计数器) 例如:
a = 10
b = a
可变和不可变的引用
python中,可变的数据类型有:列表,字典和set;不可变的类型有:数值(int,float,bool),字符串和元组 不可变类型 ,修改变量的值的时候,指针指向的内存地址由修改前指向修改后 可变类型,修改变量的值的时候,是在原对象上修改对象本身的数据,不会更改指针的指向
print ( "*******修改不可变类型的数值,指针的指向发生变化,对象本身不会变化*****" )
a = 1
b = 2
print ( id ( a) , id ( b) )
print ( "**********修改可变类型,指针无变化,对象本身的数据变化********" )
data = [ 1 , 3 ]
print ( id ( data) )
data. append( 2 )
print ( id ( data) )
== == == == == == == == == == == == == run_result== == == == == == == == == == == == == =
** ** ** * 修改不可变类型的数值,指针的指向发生变化,对象本身不会变化** ** *
1797103774000 1797103774032
** ** ** ** ** 修改可变类型,指针无变化,对象本身的数据变化** ** ** **
1797109137600
1797109137600
小整数池和大整数池
python中,自动将 -5 ~ 256 之间的整数进行了缓存,当将这些整数赋值给变量的时候,并不会重新创建对象,而是使用创建好的缓存对象**(小整数池)** python会将一定规则的字符串在字符串驻留池 中,创建一份,当将这些字符串赋值给变量时,并不会重新创建对象,而是使用字符串驻留池中创建好的对象**(大整数池)**
在字符串驻留池中的字符串 或者**-5 ~ 256之间的数值**,赋值给不同的变量后,其内存地址(id查看)一致,使用is语句判断时结果为True 在字符串驻留池之外的字符串 或者**-5 ~ 256之外的数值**,赋值给不同的变量后,其内存地址(id查看)不一致,使用is语句判断时结果为TFalse 判断两个对象是否是一个有两种方式:
实现必须是使用ipython查看
In [ 1 ] : a = 257
In [ 2 ] : b = 257
In [ 3 ] : print ( id ( a) , id ( b) )
4334648272 4334648720
In [ 4 ] : c = 10
In [ 5 ] : d = 1
In [ 7 ] : print ( id ( c) , id ( d) )
4297966096 4297965808
In [ 8 ] : d = 10
In [ 10 ] : print ( id ( c) , id ( d) )
4297966096 4297966096
** ** ** ** ** ** ** ** ** ** ** ** ** ** * is 判断** ** ** ** ** ** ** ** ** ** ** **
In [ 1 ] : a = 10
In [ 2 ] : b = 10
In [ 3 ] : a is b
Out[ 3 ] : True
In [ 4 ] : x = 2000
In [ 5 ] : y = 2000
In [ 6 ] : x is y
Out[ 6 ] : False
intern机制
创建新的变脸指向字符串对象A时,会现在缓存的池子中查找是否已经A对象,如果有,直接拿过来引用,如果没有则新建 这样的好处是:避免繁琐的创建和销毁内存,提升效率
In [ 1 ] : a = "abc"
In [ 2 ] : b= "abc"
In [ 3 ] : c = "xyz"
In [ 5 ] : print ( id ( a) , id ( b) , id ( c) )
1331287095728 1331287095728 1331355521072
深浅copy
有一篇文章讲解的很好,再次不再赘述,只做总结:《链接》 python中有三种copy,赋值、浅copy(copy.copy
)和深copy(copy.deepcopy
) 从原理上说:
赋值其实就是将新赋值的变量的指针指向元素本身,和原变量指针指向的对象一致
浅copy,新创建内存地址放置原变量最外层的元素,不包含嵌套元素,嵌套元素可以理解为只存储了一个变量名,指针指向没有记录
深copy,新创建内存地址放置原变量全部的元素,包含可变类型中的嵌套元素都重新开辟内存地址放置 深浅copy一般在嵌套列表的时候,进行讨论
不可变类型
对于不可变元素(字符串、元组和数字),经过赋值、浅copy和深copy后,其内存地址和信息会一模一样 改变其中任意一个元素,被改变的元素对象本身的数据和内存地址都会发生变化 赋值、浅copy和深copy后的对象和内存地址不会随原变量的修改而修改
import copy
list0 = 'hello,world'
list1 = copy. copy( list0)
list2 = copy. deepcopy( list0)
list3 = list0
print ( '==========修改前==========' )
print ( "原字符串:{},内存地址:{}" . format ( list0, id ( list0) ) )
print ( "浅copy字符串:{},内存地址:{}" . format ( list1, id ( list1) ) )
print ( "深copy字符串:{},内存地址:{}" . format ( list2, id ( list2) ) )
print ( "=获得的字符串:{},内存地址:{}" . format ( list3, id ( list3) ) )
list0 = list0 + 'good evening'
print ( '==========修改后==========' )
print ( "原字符串:{},内存地址:{}" . format ( list0, id ( list0) ) )
print ( "浅copy字符串:{},内存地址:{}" . format ( list1, id ( list1) ) )
print ( "深copy字符串:{},内存地址:{}" . format ( list2, id ( list2) ) )
print ( "=获得的字符串:{},内存地址:{}" . format ( list3, id ( list3) ) )
** ** ** ** ** ** ** ** ** ** ** * run_result** ** ** ** ** ** ** ** **
== == == == == 修改前== == == == ==
原字符串:hello, world,内存地址:1884431123824
浅copy字符串:hello, world,内存地址:1884431123824
深copy字符串:hello, world,内存地址:1884431123824
= 获得的字符串:hello, world,内存地址:1884431123824
== == == == == 修改后== == == == ==
原字符串:hello, worldgood evening,内存地址:1884431168496
浅copy字符串:hello, world,内存地址:1884431123824
深copy字符串:hello, world,内存地址:1884431123824
= 获得的字符串:hello, world,内存地址:1884431123824
可变类型
对于可变元素,赋值、浅copy和深copy生成的变量
浅copy和深copy,只是数据对象和原变量相等,但是内存地址不一致 修改原变量非嵌套元素的值:
浅copy和深copy的元素对象不会发生变化(下面代码中,列表中新增的元素7) 修改原变量中,嵌套元素的值:
import copy
list0 = [ 1 , 2 , 3 , 4 , 5 , 6 , { "name" : "age" } ]
list1 = copy. copy( list0)
list2 = copy. deepcopy( list0)
list3 = list0
print ( '==========修改前==========' )
print ( "原字符串:{},内存地址:{}" . format ( list0, id ( list0) ) )
print ( "浅copy字符串:{},内存地址:{}" . format ( list1, id ( list1) ) )
print ( "深copy字符串:{},内存地址:{}" . format ( list2, id ( list2) ) )
print ( "=获得的字符串:{},内存地址:{}" . format ( list3, id ( list3) ) )
print ( "打印嵌套元素的信息:{},地址信息:{}" . format ( list0[ 6 ] , id ( list0[ 6 ] ) ) )
list0[ 6 ] [ "age" ] = 20
list0. append( 7 )
print ( '==========修改后==========' )
print ( "原字符串:{},内存地址:{}" . format ( list0, id ( list0) ) )
print ( "浅copy字符串:{},内存地址:{}" . format ( list1, id ( list1) ) )
print ( "深copy字符串:{},内存地址:{}" . format ( list2, id ( list2) ) )
print ( "=获得的字符串:{},内存地址:{}" . format ( list3, id ( list3) ) )
print ( "打印浅copy嵌套元素的信息:{},地址信息:{}" . format ( list1[ 6 ] , id ( list1[ 6 ] ) ) )
print ( "打印深copy嵌套元素的信息:{},地址信息:{}" . format ( list2[ 6 ] , id ( list2[ 6 ] ) ) )
** ** ** ** ** ** ** ** ** ** ** ** ** ** run_result** ** ** ** ** ** ** ** *
== == == == == 修改前== == == == ==
原字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' } ] ,内存地址:2060355547648
浅copy字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' } ] ,内存地址:2060355547520
深copy字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' } ] ,内存地址:2060355547456
= 获得的字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' } ] ,内存地址:2060355547648
打印嵌套元素的信息:{ 'name' : 'age' } , 地址信息:2060350729088
== == == == == 修改后== == == == ==
原字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' , 'age' : 20 } , 7 ] ,内存地址:2060355547648
浅copy字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' , 'age' : 20 } ] ,内存地址:2060355547520
深copy字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' } ] ,内存地址:2060355547456
= 获得的字符串:[ 1 , 2 , 3 , 4 , 5 , 6 , { 'name' : 'age' , 'age' : 20 } , 7 ] ,内存地址:2060355547648
打印嵌套元素的信息:{ 'name' : 'age' , 'age' : 20 } , 地址信息:2060350729088
打印浅copy嵌套元素的信息:{ 'name' : 'age' , 'age' : 20 } , 地址信息:1540305791872
打印深copy嵌套元素的信息:{ 'name' : 'age' } , 地址信息:1540305792448
垃圾回收
垃圾回收机制,使用与句话来形容:引用计数机制为主,标记、清除和分代收集技术为辅的策略 引用计数:每个对象创建之后,都有一个引用计数,当引用计数为0时,name此时的垃圾回收机制就会将其销毁,回收内存空间 引用计数存在一个缺点:那就是当两个对象出现循环引用的时候,最终这两个对象始终不会被销毁,这样就会造成内存泄漏 下图中,Li1
和Li2
不再指向列表,但是列表内部存在循环引用 为了解决循环引用的问题,python引进了标记清除的当时 标记清除分为两个阶段:打标记和清除目标
会先将python中的引用分为活动对象和非活动对象