python 内存管理机制和深拷贝浅拷贝(看完解决面试题)

内存管理

面试问题:python的内存管理机制
以引用计数为主,分代回收、标记清除为辅的垃圾回收机制

以及对小整形进行缓存和简单字符串驻留的内存池机制

接下来详细介绍Python的内存管理机制

一、引用计数

引用计数
python中的每个对象都维护一个引用计数 ob_ref字段
有新的引用指向该对象的时候引用计数+1
当有无效的引用的发生的时候引用计数-1
最后引用计数为0的时候,销毁对象,该对象可以被回收,对象占用的内存空间将被释放

优点

简单 实时性

缺点:

它的缺点是需要额外的空间维护引用计数,这个问题是其次的
最主要的问题是它不能解决对象的“循环引用”

1.1 循环引用

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

getrefcount

当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1

>>> x = [1]
>>> y = [2]
>>> x.append(y)
>>> y.append(x)
>>> getrefcount(x)  
3
# 实际的引用计数比getrefcount得到的值小1,实际的引用计数为 2
>>> getrefcount(y)
3
>>> del x
>>> del y
>>> getrefcount(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

二、垃圾回收机制

2.1 python采用的垃圾回收机制:

分代回收: 启动垃圾回收的时候确定回收哪些对象的

在这里插入图片描述

标记清除: 主要解决循环引用

image.png

三种情况触发垃圾回收

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

2.2 内存池

2.2.1 小整形的缓存池
# 预先创建好了一个小整形池[-5,256]
>>> a = 1
>>> getrefcount(a)
801
>>> a = 3
>>> getrefcount(a)
48
>>> a = 300
>>> getrefcount(a)
2
# 测试边界
>>> a = 256
>>> getrefcount(a)
3
>>> a = 257
>>> getrefcount(a)
2
>>> a = -6
>>> getrefcount(a)
2
>>> a = -5
>>> getrefcount(a)
3
2.2.2 字符串驻留区

单个字符创建之后会存放在字符串驻留区
多个字符,创建之后如果没有特殊字符,就会存放在字符串驻留区

# 多个字符
>>> str1 = 'abcxyz'
>>> getrefcount(str1)
2
>>> str2 = 'abcxyz'
>>> getrefcount(str2)
3
>>> id(str1)
140181150973544
>>> id(str2)
140181150973544

# 多个字符有特殊字符
>>> str1 = 'abc xyz'
>>> str2 = 'abc xyz'
>>> id(str1)
140181150486688
>>> id(str2)
140181150486744
# 多个字符没有特殊字符
>>> str1 = 'abc123'
>>> str2 = 'abc123'
>>> id(str1)
140181150973544
>>> id(str2)
140181150973544
# 单个字符
>>> str1 = '#'
>>> str2 = '#'
>>> id(str1)
140181152088560
>>> id(str2)
140181152088560

深拷贝与浅拷贝

数字和字符串、元组,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。
列表、字典、集合可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object)

深拷贝,浅拷贝 只会发生在容器类型里面包含其他可变容器类型的情况

面试题:深拷贝和浅拷贝的区别?

浅拷贝:是将对象的引用复制给另一个对象,因此如果我们在副本中进行修改时会影响原对象。

深拷贝:是将对象本身复制给另一个对象,这意味着如果对对象的副本进行更改时不会影响原对象。

扩展:只有一种场景有深浅拷贝的区别: 容器类型里面包含其他可变容器类型的情况。
浅拷贝可能会造成修改拷贝之后的值,会改变原来的值——浅拷贝是拷贝的容器里面第一层数据的地址
深拷贝不会发生这个情况,使用copy模块的deepcopy()函数才是深拷贝——深拷贝是拷贝容器里面每一层数据

1.浅拷贝

浅拷贝只会拷贝第一层的地址
浅拷贝当改变原来的值的时候,可能会造成修改拷贝之后的值
除了copy.deepcopy是深拷贝,其他都是浅拷贝

>>> a = {"name":'sc',"score":[80,90,100]}
>>> b = a.copy()
>>> a
{'name': 'sc', 'score': [80, 90, 100]}
>>> b
{'name': 'sc', 'score': [80, 90, 100]}
>>> b["score"].append(110)
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}

# 除了copy.deepcopy是深拷贝,其他都是浅拷贝
>>> lst = [[]]*3
>>> lst
[[], [], []]
>>> lst[0].append(1)
>>> lst
[[1], [1], [1]]
>>> id(lst[0])
140181150989000
>>> id(lst[1])
140181150989000
>>> id(lst[2])
140181150989000

2.深拷贝

深拷贝不会发生改变拷贝之后改变值的情况
深拷贝就是拷贝每一层数据
使用copy模块的deepcopy函数进行的拷贝就是深拷贝

>>> import copy
>>> b = copy.deepcopy(a)  #进行深拷贝
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> id(a)
140181150994008
>>> id(b)
140181150993504
>>> id(a['score'])
140181150989000
>>> id(b['score'])
140181018248520
>>> b['score'].append(120)
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110, 120]}
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}

练习

mylist = []
for i in range(3):
    mylist.append([0,0])
print(mylist)
mylist[0][0] = 1
print(mylist)
# [[0, 0], [0, 0], [0, 0]]
# [[1, 0], [0, 0], [0, 0]]

mylist = [[0]*2]*3
print(mylist)
mylist[0][0] = 1
print(mylist)
# [[0, 0], [0, 0], [0, 0]]
# [[1, 0], [1, 0], [1, 0]]

# 两者是初始化列表是不一样的
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值