python学习——内存管理、引用、深浅拷贝、垃圾回收机制

对象引用

引用计数

  • 变量:通过变量指针引用对象
    • 变量指针指向具体对象的内存空间,取对象的值
  • 对象:类型已知,每个对象都包含一个头部信息(头部信息:类型标识赫然引用计数器)
  • 例如:
a = 10 
# 此时a这个变量的指针指向10这个对象
# 在10这个对象中,头部中包含的信息为对象的类型int和指向这个变量的计数:1
b = a
# 此时b引用a这个变量,a引用的是10这个对象
# 因此b实际上指向的也是10这个对象,此时10这个对象的计数器是:2  

可变和不可变的引用

  • 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
  • 判断两个对象是否是一个有两种方式:
    • id函数,确认对象的内存地址是否一致
    • is函数,返回True,就是一个对象
  • 实现必须是使用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) # 不可变类型copy
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)
  • 修改原变量中,嵌套元素的值:
    • 赋值的变量随原变量的变化而变化
    • 浅copy中,嵌套元素对象随原元素的变化而变化
    • 深copy中,对象元素信息不会发生变化
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此时的垃圾回收机制就会将其销毁,回收内存空间
  • 引用计数存在一个缺点:那就是当两个对象出现循环引用的时候,最终这两个对象始终不会被销毁,这样就会造成内存泄漏
  • 下图中,Li1Li2不再指向列表,但是列表内部存在循环引用
    在这里插入图片描述
  • 为了解决循环引用的问题,python引进了标记清除的当时
  • 标记清除分为两个阶段:打标记和清除目标
    • 会先将python中的引用分为活动对象和非活动对象
    • 然后将那些没有标记的非活动对象进行清除回收
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值