如何实现兼具dataclass特性与字典功能的Python类?
解决方案
你可以通过两种方式实现需求:一种是利用__getattr__简化属性访问逻辑,另一种是通过元类自动为标注字段生成@property。两种方案都继承自dict,保留所有字典操作能力,同时支持通过类型标注快速定义可属性访问的字段。
方案一:利用__getattr__实现轻量版
这种方式无需额外元类,代码简洁,仅需为需要属性访问的字段添加类型标注即可:
class ApiDict(dict): def __getattr__(self, name): # 仅允许访问类标注过的字段作为属性 if name in self.__class__.__annotations__: return self[name] raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") # 定义API响应类 class Response(ApiDict): Value: str Status: int # 测试使用 rr = Response({"Value": "success", "Status": 200, "UnusedField": "extra_data"}) # 支持属性访问标注字段 assert rr.Value == "success" assert rr.Status == 200 # 支持字典方式访问任意字段(包括未标注的新增字段) assert rr["UnusedField"] == "extra_data" assert rr["Value"] == rr.Value
方案二:元类自动生成@property
如果希望完全模拟手动编写@property的效果,可以用元类自动为标注字段生成只读属性:
class ApiDictMeta(type): def __new__(cls, name, bases, namespace): # 遍历类的类型标注,为每个字段生成@property annotations = namespace.get('__annotations__', {}) for field_name in annotations: def prop_getter(self, field=field_name): return self[field] namespace[field_name] = property(prop_getter) return super().__new__(cls, name, bases, namespace) class ApiDict(dict, metaclass=ApiDictMeta): pass # 定义API响应类 class Response(ApiDict): Value: str Status: int # 测试使用 rr = Response({"Value": "test", "Status": 404, "NewField": "added_in_v2"}) assert rr.Value == "test" assert rr["Status"] == 404 assert rr["NewField"] == "added_in_v2"
方案优势
- 完全继承
dict:所有字典操作(如keys()、items()、__getitem__)都能正常使用,新增的JSON字段无需修改代码即可通过[]访问。 - 低维护成本:仅需为需要用属性访问的字段添加类型标注,无需维护所有JSON键,API版本更新时仅新增用到的标注即可。
- 平滑迁移:现有代码中
response["Value"]的写法无需改动,可逐步迁移到.Value的属性访问方式。
内容的提问来源于stack exchange,提问作者KamilCuk
相关产品推荐
相关产品推荐

