如何在Python frozen dataclass中通过其他属性计算初始化目标属性
解决方案
frozen dataclass 会重写实例的 __setattr__ 方法拦截所有属性赋值操作,以此实现实例不可修改的特性,所以直接在类内给属性赋值会报错。可以通过以下两种方案实现需求:
方案1:使用 __post_init__ 初始化计算属性(推荐)
利用 dataclass 提供的 __post_init__ 钩子(会在默认__init__执行完成后自动调用),配合基类 object 的原生__setattr__方法绕过 frozen 限制完成属性赋值,同时将需要计算的属性标记为不需要从构造参数传入:
import dataclasses from dataclasses import field @dataclasses.dataclass(frozen=True) class Car: color: str size: int model: str # init=False表示该属性不需要在实例化时传参,由类内部赋值 price: float = field(init=False) def __post_init__(self): # 执行价格计算逻辑 color_surplus = 10 if self.color == 'red' else 0 size_surplus = 10 if self.size > 10 else 5 model_surplus = 100 if self.model == 'mustang' else 0 total_price = color_surplus + size_surplus + model_surplus # 调用object的__setattr__赋值,绕过frozen的赋值拦截 object.__setattr__(self, 'price', total_price)
使用方式完全符合预期:
car = Car('red', 15, 'mustang') print(car.price) # 输出120 car.price = 200 # 抛出FrozenInstanceError,符合冻结要求
该方案的优势是price会作为dataclass的正式字段,参与原生的比较、哈希、序列化(比如转asdict/astuple)逻辑,计算仅执行一次,性能更好。
方案2:使用@property动态计算属性
如果不需要将price作为独立字段存储,也可以用属性装饰器动态计算返回值:
import dataclasses @dataclasses.dataclass(frozen=True) class Car: color: str size: int model: str @property def price(self) -> float: color_surplus = 10 if self.color == 'red' else 0 size_surplus = 10 if self.size > 10 else 5 model_surplus = 100 if self.model == 'mustang' else 0 return color_surplus + size_surplus + model_surplus
使用方式和第一种一致,该方案不需要额外占用内存存储price字段,适合计算逻辑非常简单的场景。如果计算逻辑复杂,可以搭配functools.cached_property实现缓存,3.8及以上Python版本支持该装饰器。
内容的提问来源于stack exchange,提问作者Mattia Surricchio
相关产品推荐
相关产品推荐

