Python引用尺寸为何偏大?如何缩减Hello类的内存占用?
首先得澄清一个点:你看到的224字节、208字节其实不是Python引用的大小,而是集合对象本身的内存占用,加上自定义类实例本身的开销。Python里单个引用在64位系统上确实是8字节,但自定义类实例自带对象头(包含引用计数、类型信息等),默认还会用__dict__(一个哈希表)存储属性,这些都会额外占内存。下面是几种缩减Hello类内存占用的实用方法:
1. 用__slots__替代默认的__dict__
默认情况下,Python类的每个实例都会创建一个__dict__字典来存储属性,这个哈希表有不小的内存开销。通过定义__slots__,我们可以指定实例允许的固定属性列表,让Python不再为实例生成__dict__(以及默认的__weakref__,除非你在__slots__里显式加上),能大幅减少实例本身的内存占用。
示例代码:
class Hello: __slots__ = ('ptr', 'last_ptr') # 指定允许的属性 def __init__(self): self.ptr = set() self.last_ptr = set()
测试下来,使用__slots__的实例比普通实例内存占用少很多,尤其是当你创建大量Hello实例时,效果更明显。
2. 优化属性指向的集合对象
你看到的224/208字节是空集合对象本身的内存开销(Python的集合是动态哈希表,空集合也有固定的结构开销)。如果业务场景允许,可以做以下优化:
- 复用空集合:如果多个Hello实例的初始
ptr/last_ptr都是空集合,可以把空集合设为类属性,让所有实例共享同一个对象,避免重复创建空集合的开销:
注意:如果后续需要修改某个实例的集合,要重新赋值为新的集合(比如class Hello: __slots__ = ('ptr', 'last_ptr') _EMPTY_SET = set() def __init__(self): self.ptr = self._EMPTY_SET self.last_ptr = self._EMPTY_SETself.ptr = set(self.ptr)),不然会影响所有共享该集合的实例。 - 替换为更轻量的类型:如果不需要集合的去重、快速查找特性,可以用空列表
[]替代(空列表的内存开销比空集合小),或者根据实际需求选择更合适的数据结构。
3. 使用第三方库简化内存优化
如果觉得手动写__slots__麻烦,可以用一些第三方库来自动优化类的内存占用,比如attrs或者Python 3.7+内置的dataclasses:
用内置dataclasses
from dataclasses import dataclass @dataclass(slots=True) # 启用slots优化 class Hello: ptr: set = set() last_ptr: set = set()
用attrs库
import attr @attr.s(slots=True, auto_attribs=True) class Hello: ptr: set = attr.ib(factory=set) last_ptr: set = attr.ib(factory=set)
这些库不仅会帮你自动生成__slots__,还能简化类的定义,同时保留良好的可读性。
4. 考虑用轻量级内置结构替代自定义类
如果Hello类的逻辑非常简单,只是用来存储两个引用,可以考虑用更轻量的内置结构,比如typing.NamedTuple(不可变):
from typing import NamedTuple class Hello(NamedTuple): ptr: set last_ptr: set # 使用时 hello = Hello(set(), set())
不过要注意,NamedTuple的实例是不可变的,如果需要修改属性,这种方式就不适用了。
内容的提问来源于stack exchange,提问作者q0987

