关于Python内存分配时的小秘密分享


Posted in Python onSeptember 05, 2019

前言

Python 中的sys 模块极为基础而重要,它主要提供了一些给解释器使用(或由它维护)的变量,以及一些与解释器强交互的函数。

本文将会频繁地使用该模块的getsizeof() 方法,因此,我先简要介绍一下:

  • 该方法用于获取一个对象的字节大小(bytes)
  • 它只计算直接占用的内存,而不计算对象内所引用对象的内存

这里有个直观的例子:

import sys

a = [1, 2]
b = [a, a] # 即 [[1, 2], [1, 2]]

# a、b 都只有两个元素,所以直接占用的大小相等
sys.getsizeof(a) # 结果:80
sys.getsizeof(b) # 结果:80

上例说明了一件事:一个静态创建的列表,如果只包含两个元素,那它自身占用的内存就是 80 字节,不管其元素所指向的对象是什么。

好了,拥有这把测量工具,我们就来探究一下 Python 的内置对象都藏了哪些小秘密吧。

关于Python内存分配时的小秘密分享

1、空对象不是“空”的!

对于我们熟知的一些空对象,例如空字符串、空列表、空字典等等,不知道大家是否曾好奇过,是否曾思考过这些问题:空的对象是不是不占用内存呢?如果占内存,那占用多少呢?为什么是这样分配的呢?

直接上代码吧,一起来看看几类基本数据结构的空对象的大小:

import sys
sys.getsizeof("")  # 49
sys.getsizeof([])  # 64
sys.getsizeof(())  # 48
sys.getsizeof(set()) # 224
sys.getsizeof(dict()) # 240

# 作为参照:
sys.getsizeof(1)  # 28
sys.getsizeof(True) # 28

可见,虽然都是空对象,但是这些对象在内存分配上并不为“空”,而且分配得还挺大(记住这几个数字哦,后面会考)。

排一下序:基础数字<空元组 < 空字符串 < 空列表 < 空集合 < 空字典。

这个小秘密该怎么解释呢?

因为这些空对象都是容器,我们可以抽象地理解:它们的一部分内存用于创建容器的骨架、记录容器的信息(如引用计数、使用量信息等等)、还有一部分内存则是预分配的。

2、内存扩充不是均匀的!

空对象并不为空,一部分原因是 Python 解释器为它们预分配了一些初始空间。在不超出初始内存的情况下,每次新增元素,就使用已有内存,因而避免了再去申请新的内存。

那么,如果初始内存被分配完之后,新的内存是怎么分配的呢?

import sys
letters = "abcdefghijklmnopqrstuvwxyz"

a = []
for i in letters:
 a.append(i)
 print(f'{len(a)}, sys.getsizeof(a) = {sys.getsizeof(a)}')
 
b = set()
for j in letters:
 b.add(j)
 print(f'{len(b)}, sys.getsizeof(b) = {sys.getsizeof(b)}')

c = dict()
for k in letters:
 c[k] = k
 print(f'{len(c)}, sys.getsizeof(c) = {sys.getsizeof(c)}')

分别给三类可变对象添加 26 个元素,看看结果如何:

关于Python内存分配时的小秘密分享

由此能看出可变对象在扩充时的秘密:

  • 超额分配机制: 申请新内存时并不是按需分配的,而是多分配一些,因此当再添加少量元素时,不需要马上去申请新内存
  • 非均匀分配机制: 三类对象申请新内存的频率是不同的,而同一类对象每次超额分配的内存并不是均匀的,而是逐渐扩大的

3、列表不等于列表!

以上的可变对象在扩充时,有相似的分配机制,在动态扩容时可明显看出效果。

那么,静态创建的对象是否也有这样的分配机制呢?它跟动态扩容比,是否有所区别呢?

先看看集合与字典:

# 静态创建对象
set_1 = {1, 2, 3, 4}
set_2 = {1, 2, 3, 4, 5}
dict_1 = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5}
dict_2 = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5, 'f':6}

sys.getsizeof(set_1) # 224
sys.getsizeof(set_2) # 736
sys.getsizeof(dict_1) # 240
sys.getsizeof(dict_2) # 368

看到这个结果,再对比上一节的截图,可以看出:在元素个数相等时,静态创建的集合/字典所占的内存跟动态扩容时完全一样。

这个结论是否适用于列表对象呢?一起看看:

list_1 = ['a', 'b']
list_2 = ['a', 'b', 'c']
list_3 = ['a', 'b', 'c', 'd']
list_4 = ['a', 'b', 'c', 'd', 'e']

sys.getsizeof(list_1) # 80
sys.getsizeof(list_2) # 88
sys.getsizeof(list_3) # 96
sys.getsizeof(list_4) # 104

上一节的截图显示,列表在前 4 个元素时都占 96 字节,在 5 个元素时占 128 字节,与这里明显矛盾。

所以,这个秘密昭然若揭:在元素个数相等时,静态创建的列表所占的内存有可能小于动态扩容时的内存!

也就是说,这两种列表看似相同,实际却不同!列表不等于列表!

4、消减元素并不会释放内存!

前面提到了,扩充可变对象时,可能会申请新的内存。

那么,如果反过来缩减可变对象,减掉一些元素后,新申请的内存是否会自动回收掉呢?

import sys
a = [1, 2, 3, 4]
sys.getsizeof(a) # 初始值:96
a.append(5)  # 扩充后:[1, 2, 3, 4, 5]
sys.getsizeof(a) # 扩充后:128
a.pop()   # 缩减后:[1, 2, 3, 4]
sys.getsizeof(a) # 缩减后:128

如代码所示,列表在一扩一缩后,虽然回到了原样,但是所占用的内存空间可没有自动释放啊。其它的可变对象同理。

这就是 Python 的小秘密了,“胖子无法减重原理” :瘦子变胖容易,缩减身型也容易,但是体重减不掉,哈哈~~~

5、空字典不等于空字典!

使用 pop() 方法,只会缩减可变对象中的元素,但并不会释放已申请的内存空间。

还有个 clear() 方法,它会清空可变对象的所有元素,让我们试试看吧:

import sys
a = [1, 2, 3]
b = {1, 2, 3}
c = {'a':1, 'b':2, 'c':3}

sys.getsizeof(a) # 88
sys.getsizeof(b) # 224
sys.getsizeof(c) # 240

a.clear()  # 清空后:[]
b.clear()  # 清空后:set()
c.clear()  # 清空后:{},也即 dict()

调用 clear() 方法,我们就获得了几个空对象。

在第一小节里,它们的内存大小已经被查验过了。(前面说过会考的,请默写 回看下)

但是,如果这时再去查验的话,你会惊讶地发现,这些空对象的大小跟前面查的并不完全一样!

# 承接前面的清空操作:
sys.getsizeof(a) # 64
sys.getsizeof(b) # 224
sys.getsizeof(c) # 72

空列表与空元组的大小不变,然而空字典(72)竟然比前面的空字典(240)要小很多!

也就是说,列表与元组在清空元素后,回到起点不变初心,然而,字典这家伙却是“赔了夫人又折兵”,不仅把“吃”进去的全吐出来了,还把自己的老本给亏掉了!

字典的这个秘密藏得挺深的,说实话我也是刚刚获知,百思不得其解……

以上就是 Python 在分配内存时的几个小秘密啦,看完之后,你是否觉得涨见识了呢?

你想明白了几个呢,又产生了多少新的谜团呢?欢迎留言一起交流哦~

对于那些没有充分解释的小秘密,今后我们再慢慢揭秘……

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python实现1-9数组形成的结果为100的所有运算式的示例
Nov 03 Python
Python reduce()函数的用法小结
Nov 15 Python
Python如何抓取天猫商品详细信息及交易记录
Feb 23 Python
Pandas 数据处理,数据清洗详解
Jul 10 Python
python中使用 xlwt 操作excel的常见方法与问题
Jan 13 Python
pandas如何处理缺失值
Jul 31 Python
python gdal安装与简单使用
Aug 01 Python
python求最大公约数和最小公倍数的简单方法
Feb 13 Python
浅谈Django QuerySet对象(模型.objects)的常用方法
Mar 28 Python
使用opencv中匹配点对的坐标提取方式
Jun 04 Python
python进行OpenCV实战之画图(直线、矩形、圆形)
Aug 27 Python
Python获取指定日期是"星期几"的6种方法
Mar 13 Python
python global关键字的用法详解
Sep 05 #Python
python requests证书问题解决
Sep 05 #Python
Python使用scipy模块实现一维卷积运算示例
Sep 05 #Python
Python图像处理模块ndimage用法实例分析
Sep 05 #Python
Pycharm+django2.2+python3.6+MySQL实现简单的考试报名系统
Sep 05 #Python
PyCharm搭建Spark开发环境的实现步骤
Sep 05 #Python
浅谈Python_Openpyxl使用(最全总结)
Sep 05 #Python
You might like
ninety plus是什么?ninety plus咖啡好吗?
2021/03/04 新手入门
详解WordPress开发中wp_title()函数的用法
2016/01/07 PHP
深入研究PHP中的preg_replace和代码执行
2018/08/15 PHP
laravel 中某一字段自增、自减的例子
2019/10/11 PHP
使用PHP+JQuery+Ajax分页的实现
2013/04/23 Javascript
js处理json以及字符串的比较等常用操作
2013/09/08 Javascript
JavaScript代码简单实现求杨辉三角给定行的最大值
2013/10/29 Javascript
JS方法调用括号的问题探讨
2014/01/24 Javascript
jQuery的cookie插件实现保存用户登陆信息
2014/04/15 Javascript
jquery通过select列表选择框对表格数据进行过滤示例
2014/05/07 Javascript
seajs加载jquery时提示$ is not a function该怎么解决
2015/10/23 Javascript
javascript简单实现等比例缩小图片的方法
2016/07/27 Javascript
基于BootStrap实现简洁注册界面
2017/07/20 Javascript
JavaScript实现简单评论功能
2017/08/17 Javascript
详解express使用vue-router的history踩坑
2019/06/05 Javascript
详解Node.js使用token进行认证的简单示例
2020/05/25 Javascript
如何搭建一个完整的Vue3.0+ts的项目步骤
2020/10/18 Javascript
vue mvvm数据响应实现
2020/11/11 Javascript
[57:36]DOTA2-DPC中国联赛 正赛 SAG vs CDEC BO3 第三场 2月1日
2021/03/11 DOTA
Python BeautifulSoup中文乱码问题的2种解决方法
2014/04/22 Python
Python之py2exe打包工具详解
2017/06/14 Python
对pandas的dataframe绘图并保存的实现方法
2017/08/05 Python
python unittest实现api自动化测试
2018/04/04 Python
Python二进制文件读取并转换为浮点数详解
2019/06/25 Python
解决使用export_graphviz可视化树报错的问题
2019/08/09 Python
python应用文件读取与登录注册功能
2019/09/23 Python
用Python去除图像的黑色或白色背景实例
2019/12/12 Python
python paramiko远程服务器终端操作过程解析
2019/12/14 Python
在tensorflow中设置使用某一块GPU、多GPU、CPU的操作
2020/02/07 Python
python模块如何查看
2020/06/16 Python
食品业务员岗位职责
2014/03/18 职场文书
大学生学雷锋活动总结
2014/06/26 职场文书
护士优质服务演讲稿
2014/08/26 职场文书
总经理助理岗位职责
2015/01/31 职场文书
新闻稿格式范文
2015/07/18 职场文书
2016年全国爱眼日宣传教育活动总结
2016/04/05 职场文书