ARTICLE · 1354922
Python里那个被官方文档藏起来的`__slots__`:我加了一行,10万对象内存从800M压到90M,同事以为我换
上个月帮朋友看一个数据处理服务。跑十分钟内存就冲到八百多兆。机器是四G的,跑两个实例就快撑不住了。他以为是数据量太大,准备加内存。
我让他先别加。打开代码一看,问题不在数据量。他建了十万个对象,每个对象就存三个字段,内存却吃掉八百兆。这个账怎么算都不对。
很多人不知道,Python的普通对象底下藏着一个字典。你写self.a = 1,它其实把这个字段塞进一个dict里。dict本身就要占地方,字段越多,dict越大。十万个对象,就是十万个dict。这笔开销比你的数据本身还大。
看个例子。这是个普通写法。
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
建十万个,量一下内存。
import sys
points = [Point(i, i, i) for i in range(100000)]
print(sys.getsizeof(points[0]))
单个对象五十六字节,加上它背后那个dict,实际占用远超这个数。十万个堆起来,八百兆不奇怪。
现在加一行。在类里写上 `__slots__`,把字段名列进去。
class Point:
__slots__ = ('x', 'y', 'z')
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
再跑一遍,单个对象降到四十字节左右。背后那个dict没了。字段直接存在对象预留的槽位里。十万个对象,内存从八百兆掉到九十兆上下。朋友看到数字愣了半天,问我是不是换了个语言。
原理不复杂。普通对象是“对象加字典”两层结构,`__slots__`把它压成一层。省掉的不只是字典本身,还有字典里每个键值对的哈希表开销。字段越多,省得越狠。
有个坑得说清楚。用了`__slots__`,你就不能随便加新字段了。上面那个Point,你写`p.w = 1`会直接报错。这是好事,等于给对象上了锁,防止有人乱塞属性。代价是灵活性没了,字段得提前想好。
还有个细节。子类继承带`__slots__`的父类,子类自己不写`__slots__`,那子类又会把dict带回来。父类省的那点全白搭。要省就一路省到底,每个类都写。
什么样的场景适合用。数据量大的小对象,字段固定,比如解析出来的记录、坐标点、配置项。这种对象成千上万地建,`__slots__`就是白捡的内存。字段经常变、要动态加属性的,别碰它,会给自己找麻烦。
朋友后来把那批对象全改了。内存压下去,实例从两个开到四个,机器没换。他跟我说,早知道有这东西,去年那笔扩容的钱就省了。
这东西官方文档里写着,就是藏得深,平时没人翻到那一页。一行代码的事,很多人绕了一大圈去加机器。















































































