Python 内存管理-深浅拷贝

本文详细介绍了Python的内存管理机制,包括引用计数、垃圾回收的原理与触发条件,以及分代回收和标记清除策略。同时,讲解了整数对象缓冲池和字符串驻留区的概念,以及浅拷贝与深拷贝的区别。通过对这些概念的理解,有助于编写更高效、健壮的Python代码。
摘要由CSDN通过智能技术生成

1、内存管理机制

引用计数为主,分代回收、标记清除为辅的垃圾回收方式
以及对小整型进行缓存和简单字符驻留内存池机制

1.1 引用计数

ptython 中的每个对象都维护一个引用计数 ob_ref字段
- 当有新的引用指向改对象的时候,引用计数+1
- 当无效的引用发生的时候,引用计数-1
- 最后引用技术为0,销毁对象
>>> from sys import getrefcount
>>> a = 1000
>>> getrefcount(a)  # getrefcount() 获取引用计数
2   # 当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。
>>> b = a
>>> getrefcount(a)
3
>>> c = []
>>> c.append(b)
>>> getrefcount(a)
4
>>> d = 2000
>>> e = 2000
>>> getrefcount(d)
2
>>> getrefcount(e)
2
>>> d = e = 600
>>> getrefcount(d)
3
>>> getrefcount(e)
3

循环引用

根据引用计数的规律,出现循环引用的情况,内存是无法通过引用计数来释放的,这种情况就会造成内存泄漏。
内存泄漏: 有一部分内存被占用无法释放,进程无法访问
后果: 造成内存溢出(oom—out of memory),内存不够,程序需要的内存大于系统的空闲内存

>>> x = []
>>> x = [1]
>>> y = [2]
>>> x.append(y)
>>> y.append(x)
>>> y
[2, [1, [...]]]  # 发生死循环
>>> x
[1, [2, [...]]]
>>> getrefcount(x)
3
>>> getrefcount(y)
3
>>> del x  # del x;del y引用删除,这块内存区域获取不到了
>>> del y

优缺点

优点:简单、实时性
缺点:维护引用计数消耗资源、循环引用无法回收

1.2 垃圾回收

1) 回收原则

•当Python的某个对象的引用计数降为0时,可以被垃圾回收

2)三种情况触发垃圾回收

• 调用gc.collect()
• GC达到阀值时
• 程序退出时

3)gc机制

• GC作为现代编程语言的自动内存管理机制,专注于两件事
• 找到内存中无用的垃圾资源
• 清除这些垃圾并把内存让出来给其他对象使用。
GC彻底把程序员从资源管理的重担中解放出来,让他们有更多的时间放在业务逻辑上。但这并不意味 着码农就可以不去了解GC,毕竟多了解GC知识还是有利于我们写出更健壮的代码

>>> import gc
>>> print (gc.get_threshold())
(700, 10, 10)      # 
>>> gc.collect()   #调用gc.collect()回收
2
>>> gc.collect()   #再次调用就没有要回收的了
0

4)回收方式

a.分代(generation)回收

启动垃圾回收的时候确定扫描哪些对象

这一策略的基本假设是:存活时间越久的对象,越不可能在后面的程序中变成垃圾。

  • Python将所有的对象分为0,1,2三代。 
  • 所有的新建对象都是0代对象。 
  • 当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。
  • 垃圾回收启动时,一定会扫描所有的0代对象。
  • 如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。
  • 当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。
b.标记清除

主要解决循环引用

标记-清除机制,顾名思义,首先标记对象(垃圾检测),然后清除垃圾(垃圾回收)。 主要用于解决循环引用。

 1. 标记:活动(有被引用), 非活动(可被删除) 
 2. 清除:清除所有非活动的对象

1.3 缓冲池

1)整数对象缓冲池

对于[-5,256] 这样的小整数,系统已经初始化好,可以直接拿来用。而对于其他的大整数,系统则提 前申请了一块内存空间,等需要的时候在这上面创建大整数对象。
注:对于乘法创建的字符 只会缓冲20个

>>> a = 1			# a和b是一样的
>>> b = 1			# python的整数对象缓冲池
>>> id(a)
140133544871840	# 内存地址一样
>>> id(b)
140133544871840	# 内存地址一样
>>> a = 777		# a和b不是一样的
>>> b = 777
>>> id(a)			# 内存地址不同
140133545530064	
>>> id(b)			# 内存地址不同
140133545530384
>>> a = b = 777
>>> id(a)
140133545530480
>>> id(b)
140133545530480 

2)字符串驻留区

为了检验两个引用指向同一个对象,我们可以用is关键字。is用于判断两个引用所指的对象是否相同。 当触发缓存机制时,只是创造了新的引用,而不是对象本身。

>>> str1 = "abcxyz"   不包含特殊字符的字符串,会放到驻留区
>>> getrefcount(str1) 
2
>>> str2 = "abcxyz"
>>> getrefcount(str1)
3
>>> id(str1)
139788884979192
>>> id(str2)
139788884979192
>>> str3 = "#"   单个特殊字符也是会放在驻留区的
>>> str4 = "#"
>>> id(str3)
139788884976056
>>> id(str4)
139788884976056
>>> str5 = "abc 123"    包含了特殊字符(空格),不放在驻留区
>>> str6 = "abc 123"
>>> id(str5)
139788853780696
>>> id(str6)
139788853780808

2、深拷贝与浅拷贝

不属于内存管理
# 只会发生在容器类型里面包含其他可变容器类型的情况
# 浅拷贝可能会造成修改拷贝之后的值
# 浅拷贝只会拷贝第一层的地址,深拷贝则不会

浅拷贝

使用copy()方法,拷贝的是可变对象里的每一个元素的引用地址。整体的引用地址不同

a = {"name": "sc", "score": [80, 90, 100]}
b = a.copy()  # 浅拷贝,拷贝a里面每个元素(列表算一个元素)的引用地址
print(id(a), id(b))  # 2346909244224 2346909244416
print(id(a["score"]), id(b["score"]))  # 2554998043648 2554998043648
b["score"].append(110)  
print(a, b)  # 都是{'name': 'sc', 'score': [80, 90, 100, 110]}
print(id(a), id(b))  # 2346909244224 2346909244416
print(id(a["score"]), id(b["score"]))  # 2554998043648 2554998043648

使用* 生成的引用,指向的是同一个对象(浅拷贝)

lst = [[]] * 3 # 引用的 [] 是同一个内存地址
print(id(lst), id(lst[0]), id(lst[1]), id(lst[2]))
# 1988547540864 1988544640000 1988544640000 1988544640000
lst[0].append(1)
print(lst)
# [[1], [1], [1]]
print(id(lst), id(lst[0]), id(lst[1]), id(lst[2]))
# 1988547540864 1988544640000 1988544640000 1988544640000

深拷贝

容器里面包含一个可变的容器,才会有深拷贝。
b = copy.deepcopy(a) 只有这种是深拷贝,其他情况都是浅拷贝

import copy

a = {"name": "sc", "score": [80, 90, 100]}
b = copy.deepcopy(a)
print(id(a), id(b))  # 1435380095872 1435380196672
print(id(a["score"]), id(b["score"]))  # 1435380502016 1435380501632

b["score"].append(120)
print(a, b)  # {'name': 'sc', 'score': [80, 90, 100]} {'name': 'sc', 'score': [80, 90, 100, 120]}
print(id(a), id(b))  # 1435380095872 1435380196672
print(id(a["score"]), id(b["score"]))  # 1435380502016 1435380501632
b = []
print(b, id(b)) # [] 2026847155200
for i in range(3):
    b.append([])  # 3个不同对象

print(b, id(b)) # [[], [], []] 2026847155200
print(id(b[0]), id(b[1]), id(b[2])) # 2026847155136 2026850055872 2026847157952
b[0].append(1)

print(b, id(b)) # [[1], [], []] 2026847155200
print(id(b[0]), id(b[1]), id(b[2])) # 2026847155136 2026850055872 2026847157952
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这个手刹不太灵儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值