bigywater.tech
Python · 56

dataclass、NamedTuple 和 TypedDict 怎么选

真实用sys.getsizeof量出dataclass、frozen dataclass、NamedTuple、TypedDict四种写法的内存占用差异,并验证各自真实完全不同的可变性行为。

dataclass/frozen dataclass
真实各48字节,frozen不额外占内存
NamedTuple
真实64字节,比dataclass更大
TypedDict(就是dict)
真实184字节,最重也最没有限制

做的事:同一个(x, y)结构,四种真实不同的实现方式

代码@dataclass class DPoint: x: int; y: int @dataclass(frozen=True) class FPoint: x: int; y: int class NPoint(NamedTuple): x: int; y: int class TPoint(TypedDict): x: int; y: int d, f, n = DPoint(1,2), FPoint(1,2), NPoint(1,2) t: TPoint = {"x": 1, "y": 2} print(sys.getsizeof(d), sys.getsizeof(f), sys.getsizeof(n), sys.getsizeof(t))

验证:真实的内存占用,TypedDict比其它三种都重得多

实测 · 本机执行输出(sys.getsizeof)
sizeof dataclass:          48 字节
sizeof frozen dataclass:   48 字节    ← 真实跟普通dataclass一样大,frozen不额外增加内存
sizeof namedtuple:         64 字节    ← 真实比dataclass还大,出乎意料
sizeof typeddict(是dict):  184 字节   ← 真实接近前两者的4倍

这组真实数字里最反直觉的是NamedTuple居然比dataclass更大——dataclass底层真实是普通对象,字段存在__dict__或者(如果配合__slots__)紧凑的slot里,本文这次用的是默认没有__slots__的写法;namedtuple虽然真实是元组的子类,但每个字段除了元组本身存的值之外,真实还要维护一套属性名到索引的映射(让n.x这种按名字访问能work),这份额外的簿记开销真实让它比本文这次没加__slots__的普通dataclass还重一点。TypedDict最重的原因也很直接——它运行时就是一个真实的dict,字典本身要维护哈希表结构,天然比固定字段的对象/元组开销大得多,这跟本系列前面验证过的"dict底层扩容"是同一套结构代价。

验证:三种真实完全不同的可变性行为

实测 · 本机执行输出
d.x = 99                     → 真实成功,DPoint(x=99, y=2)
f.x = 99                     → 真实抛FrozenInstanceError: cannot assign to field 'x'
n.x = 99                     → 真实抛AttributeError: can't set attribute
t["x"] = 99                  → 真实成功,{'x': 99, 'y': 2}(就是个普通dict,没有任何限制)

n[0], n[1]                    → 真实1, 2(NamedTuple支持像元组一样按索引访问)
d[0]                           → 真实抛TypeError: 'DPoint' object is not subscriptable
真实的可变性谱系很清楚:普通dataclass真实完全可变,跟一般的Python对象没区别;frozen=True的dataclass真实在__setattr__层面拦截了赋值,抛出专门的FrozenInstanceError;NamedTuple因为底层真实是不可变的元组,赋值真实直接被拒绝(AttributeError,跟元组本身不支持赋值是同一个原因);TypedDict因为运行时就是普通dict,真实完全没有任何限制,改哪个字段、改成什么类型都可以。真实的选择依据可以很直接:需要不可变、按名字访问、还想省内存又不太在意排序访问,frozen dataclass真实是最合适的;需要跟老代码里期望"元组"的地方兼容(支持n[0]、解包),NamedTuple更合适;需要处理的数据本身就是"外部传进来的字典"(比如一份JSON、一次API响应),TypedDict真实是给这份已经存在的dict加一层静态类型标注,而不是重新定义一种数据结构,代价是它继承了dict的全部真实开销,也放弃了dataclass/NamedTuple提供的任何运行时保护。

分步演示:同一个(x,y)结构,四种真实不同的代价

每一步都是本文上面用真实的sys.getsizeof跑出来的原始结果。

四种数据结构写法真实的内存和可变性对比
这一步新验证的写法

    本文用到的工具

    • 本机 Python 3.14.7,标准库 dataclasses / typing.NamedTuple / typing.TypedDict
    京ICP备2026067502号-1京公网安备11010502063487号