Python中的sys模块是极其基础和重要的。它主要提供一些由解释器使用(或维护)的变量,以及一些与解释器强交互的函数。本文会经常用到该模块的getsizeof()方法,先简单介绍一下:该方法用于获取对象的字节大小(bytes)。它只计算直接占用的内存,不计算对象的内部内存。下面是一个引用对象内存的直观例子:importsysa=[1,2]b=[a,a]#即[[1,2],[1,2]]#a和b两者都只有两个元素,所以直接占用的大小是相等的,那么它自己占用的内存是80字节,不管它的元素指向什么对象.好了,有了这个测量工具,我们就来探索一下Python内置对象到底隐藏了哪些小秘密。1.空对象不“空”!对于我们熟悉的一些空对象,比如空字符串、空列表、空字典等,不知道大家有没有想过,有没有想过这些问题:空对象不占内存吗?如果占用内存,占用多少?为什么会这样分配?直接上代码,先看一下几种基本数据结构的空对象大小:importsyssys.getsizeof("")#49sys.getsizeof([])#64sys.getsizeof(())#48sys.getsizeof(set())#224sys.getsizeof(dict())#240#作为参考:sys.getsizeof(1)#28sys.getsizeof(True)#28可以看出虽然都是空对象,这些对象在内存中没有分配是“空的”,而且分配的是相当大的(记住这些数字,后面会测试)。排序方式:基数<空元组<空字符串<空列表<空集合<空字典。这个小秘密怎么解释呢?因为这些空对象都是容器,我们可以抽象地理解:它们的一部分内存用于创建容器的骨架,记录容器的信息(如引用计数、使用信息等),一部分用于内存是预先分配的。2.内存扩展不统一!空对象不是空的,部分原因是Python解释器为它们预分配了一些初始空间。在不超过初始内存的情况下,每增加一个元素,都会使用已有的内存,从而避免了申请新内存的情况。那么,如果分配了初始内存,那么新的内存又是如何分配的呢?importsysletters="abcdefghijklmnopqrstuvwxyz"a=[]foriinletters:a.append(i)print(f'{len(a)},sys.getsizeof(a)={sys.getsizeof(a)}')b=set()forjinletters:b.add(j)print(f'{len(b)},sys.getsizeof(b)={sys.getsizeof(b)}')c=dict()forkinletters:c[k]=kprint(f'{len(c)},sys.getsizeof(c)={sys.getsizeof(c)}')三类变量对象添加26个元素,见Look结果:由此可见变量对象扩展的秘密:过度分配机制:在申请新内存时,不是按需分配,而是多分配,所以在添加少量元素时,没有需要立即分配申请新内存非均匀分配机制:三种类型的对象申请新内存的频率不同,同一类型对象每次的超分配内存不是统一的,但是逐渐扩大。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)#224sys.getsizeof(set_2)#736sys.getsizeof(dict_1)#240sys.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)#80sys.getsizeof(list_2)#88sys.getsizeof(list_3)#96sys.getsizeof(list_4)#104上一节截图显示,列表前4个元素占96字节,后5个元素占128字节,明显矛盾。因此,其中的奥妙就显而易见了:当元素个数相等时,静态创建的列表占用的内存可能比动态扩展占用的内存小!也就是两个表看似相同,实则不同!列表不等于列表!4.减少元素不会释放内存!如前所述,在扩展可变对象时,可能会请求新的内存。那么,如果对可变对象进行反向reduce,在reduce一些元素后,新分配的内存会自动回收吗?importsysa=[1,2,3,4]sys.getsizeof(a)#初始值:96a.append(5)#扩展后:[1,2,3,4,5]sys.getsizeof(a)#Afterexpansion:128a.pop()#Afterreduction:[1,2,3,4]sys.getsizeof(a)#Afterreduction:128如代码所示,list扩缩容后,虽然返回到其原始状态,但占用的内存空间不会自动释放。其他可变对象也是如此。这就是Python的小秘密,《胖人瘦不下来的原理》:瘦的人很容易变胖,也很容易减体型,但是体重减不下来,哈哈~~~5.空字典不等于空字典!使用pop()方法只会减少变量对象中的元素,并不会释放分配的内存空间。还有一个clear()方法,它会清除可变对象的所有元素,让我们试试看:importsysa=[1,2,3]b={1,2,3}c={'a':1,'b':2,'c':3}sys.getsizeof(a)#88sys.getsizeof(b)#224sys.getsizeof(c)#240a.clear()#清除后:[]b.clear()#清除后:set()c.clear()#清除后:{},即dict()调用了clear()方法,我们得到了几个空对象。在第一部分中,检查了它们的内存大小。(前面说了,请默写,再读回来)然而,此时再检查,你会惊奇地发现,这些空对象的大小与之前检查的并不完全一样!#进行前面的清空操作:sys.getsizeof(a)#64sys.getsizeof(b)#224sys.getsizeof(c)#72空列表和空元组的大小保持不变,但是空字典(72)是其实比之前的空字典(240)小多了!也就是说,list和tuple清空元素后,不改变初衷,回到起点。我丢了钱!字典的秘密很深。说实话,我也是刚学,想不通。。。以上就是Python在分配内存时的几个小秘密。看完之后是不是觉得自己的知识又增长了呢?你想出了多少,又创造了多少新的谜团?欢迎大家一起留言分享哦~那些没有完全解释清楚的小秘密,以后会慢慢揭秘……作者简介:猫底下的豌豆花,出生于广东,毕业于武汉大学,现在是苏飘程序员,有一些极客思维,也有一些人文情怀,有温度,有态度。公众号:“蟒蛇猫”(python_cat)
