当前位置:首页 > 文章列表 > 文章 > python教程 > Python frozen dataclass 里怎么保存派生字段

Python frozen dataclass 里怎么保存派生字段

来源:17golang原创 2026-09-08 09:07:22 0浏览 收藏

在订单行、配置快照这类对象里,totalcache_key 之类的字段通常不是调用方直接传入,而是由输入字段计算出来。使用 @dataclass(frozen=True) 后,普通的 self.total = ... 会触发 FrozenInstanceError。稳妥的写法是把派生字段声明为 init=False,在 __post_init__ 中用 object.__setattr__ 完成一次初始化;默认列表则交给 default_factory,不要把可变对象写成类级共享默认值。

核心做法:输入字段参与生成构造参数,派生字段用 field(init=False) 排除出构造签名;frozen=True 下只在 __post_init__ 里通过 object.__setattr__ 写入一次。
要点速览
  • __post_init__ 适合计算依赖多个输入的字段。
  • default_factory 每次构造时创建独立默认值,避免列表互相污染。
  • frozen 只限制属性重新绑定,不会自动冻结列表内部内容。

为什么派生字段要放进 __post_init__

生成的 __init__ 只负责接收真实输入。把派生值标记为 init=False,可以让调用方不能绕过计算逻辑直接传入;__post_init__ 又紧跟在生成构造方法之后,适合集中处理字段之间的关系。

下面的例子模拟订单行:税率是构造阶段的临时输入,含税金额是派生字段。frozen=True 时要用 object.__setattr__,这只解决初始化写入,不代表后续代码可以随意修改实例。

from dataclasses import InitVar, dataclass, field
from decimal import Decimal

@dataclass(frozen=True)
class OrderLine:
    name: str
    base_price: Decimal
    quantity: int = 1
    tax_rate: InitVar[Decimal] = Decimal("0.13")
    total: Decimal = field(init=False)

    def __post_init__(self, tax_rate: Decimal) -> None:
        # 派生值不进入构造签名,只由输入字段统一计算
        amount = self.base_price * self.quantity
        total = amount * (Decimal("1") + tax_rate)
        # frozen 实例禁止普通赋值,初始化阶段用底层写入
        object.__setattr__(self, "total", total)

line = OrderLine("键盘", Decimal("199"), quantity=2)
print(line.total)  # 449.74

这里的 tax_rateInitVar,只参与生成的 __init____post_init__,不会成为实例字段。total 则是真正的字段,因此可被 fields() 和序列化逻辑看到。

Python frozen dataclass 中输入字段、__post_init__ 与 total 派生字段的静态关系
图1:输入字段汇入 __post_init__ 计算 total,冻结边界内只通过 object.__setattr__ 完成初始化写入。

default_factory 和字段顺序怎么一起检查

默认值和字段位置会共同决定生成的 __init__。没有默认值的字段必须放在有默认值的字段前面,这条规则也适用于继承后的字段合并。对于列表、集合等容器,写成 tags: list[str] = [] 会让多个实例共享同一个对象;应改用零参数工厂。

from dataclasses import InitVar, dataclass, field, fields

@dataclass(frozen=True)
class Job:
    name: str
    quantity: int = 1
    tags: list[str] = field(default_factory=list)
    config: InitVar[dict] = None

    def __post_init__(self, config: dict | None) -> None:
        # config 只用于初始化判断,不保存为实例字段
        if config and config.get("tag"):
            self.tags.append(config["tag"])

first = Job("build", config={"tag": "release"})
second = Job("test")
print(first.tags, second.tags)  # ['release'] []
print([item.name for item in fields(Job)])  # name, quantity, tags

示例中的 tags 是列表,所以 frozen=True 并不能阻止 append;它冻结的是属性绑定,而不是对象内部的可变内容。如果目标是更强的不可变边界,可以把类型改成 tuple[str, ...],并用 default_factory=tuple

声明进入 __init__出现在 fields()适用边界
field(init=False)保存派生字段
default_factory=list可带默认值每个实例独立容器
InitVar[dict]只在构造阶段使用的输入
frozen=True不改变参数规则不改变字段集合阻止普通属性重新赋值

生成签名不稳定时,先按这三个边界排查

第一,看必填字段是否误放在默认字段之后;第二,看 InitVar 是否只是临时依赖,若需要持久化就应改成普通字段;第三,看派生字段是否错误地保留了 init=True。需要检查真实字段集合时,用 fields(),不要把类的注解字典当成最终 dataclass 结构。

如果类使用继承,基类 dataclass 字段会参与最终顺序,子类同名字段还可能覆盖基类定义。生产代码可以把构造参数尽量改成关键字调用,减少字段新增或重排带来的调用方风险。

Python dataclass 中 default_factory、字段顺序、InitVar 和 fields 的静态关系
图2:字段顺序影响 __init__ 参数,default_factory=list 提供独立容器,InitVar 只连接初始化链路而不进入 fields()

常见问题

frozen=True 下能不能在 __post_init__ 里直接赋值?

不能。直接写 self.total = value 会触发冻结异常,应使用 object.__setattr__ 完成派生字段的初始化。

default_factory 每次都会重新调用吗?

当该字段没有显式传值时,生成的构造方法会为当前实例调用工厂,因此适合创建独立列表、集合或元组。

InitVar 适合保存数据库连接吗?

它适合接收连接、配置等构造期依赖,再在 __post_init__ 中提取需要的值;连接对象本身不会自动成为实例字段。

派生字段应该用 property 还是 init=False?

需要序列化、比较或缓存结果时可用 init=False;必须始终随输入实时变化且不想保存副本时,通常更适合用只读 property

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java groupingBy 的下游 mapping 怎么提取每组指定字段Java groupingBy 的下游 mapping 怎么提取每组指定字段
上一篇
Java groupingBy 的下游 mapping 怎么提取每组指定字段
Go 结构体复制后 mutex 为什么可能造成数据竞争
下一篇
Go 结构体复制后 mutex 为什么可能造成数据竞争
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    21次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    177次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    112次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    39次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    18次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码