夜雨聆风学习资料网

ARTICLE · 1354922

Python里那个被官方文档藏起来的`__slots__`:我加了一行,10万对象内存从800M压到90M,同事以为我换

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__`就是白捡的内存。字段经常变、要动态加属性的,别碰它,会给自己找麻烦。

朋友后来把那批对象全改了。内存压下去,实例从两个开到四个,机器没换。他跟我说,早知道有这东西,去年那笔扩容的钱就省了。

这东西官方文档里写着,就是藏得深,平时没人翻到那一页。一行代码的事,很多人绕了一大圈去加机器。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料