使用Python dataclasses将入参转换为自定义对象的问题求解
问题背景
使用Python dataclasses 开发时,dataclass本身不会按照字段的类型注解自动做类型转换:
- 向
Entry类传入字典格式的location参数时,不会自动构造为Location类实例 - 向
Entry类传入字符串格式的creationDate参数时,不会自动调用dateutil.parser.parse转为datetime.datetime对象 - 原有代码中配置的
default_factory仅在字段未传值时触发,传值场景下不会执行转换逻辑,也无法自动处理自定义嵌套类的实例化。
实现方案
直接使用dataclass内置的__post_init__后处理钩子即可实现需求,不需要额外编写冗余的工厂类逻辑,没有多余样板代码。__post_init__会在dataclass完成默认字段赋值后自动执行,我们可以在这个方法里统一做类型校验和转换:
- 对
creationDate字段:如果当前传入值是字符串类型,调用解析方法转为datetime对象 - 对
location字段:如果当前传入值是字典类型,自动过滤掉Location类不存在的冗余字段后,解包构造Location实例
修正后的可运行代码如下:
import datetime import dateutil.parser import dataclasses from typing import Optional @dataclasses.dataclass class Location(): latitude: float longitude: float @dataclasses.dataclass class Entry(): """ A single DayOne entry """ creationDate: datetime.datetime location: Optional[Location] = None text: str = "" def __post_init__(self): # 字符串时间转datetime对象 if isinstance(self.creationDate, str): self.creationDate = dateutil.parser.parse(self.creationDate) # 字典转Location实例,自动过滤多余字段 if isinstance(self.location, dict): valid_loc_fields = {f.name for f in dataclasses.fields(Location)} loc_params = {k:v for k, v in self.location.items() if k in valid_loc_fields} self.location = Location(**loc_params) if __name__ == "__main__": print("Converting from dayone to jekyll\n") args = { "creationDate": '2022-05-30T04:44:33Z', "location": { 'latitude': -37.8721, 'longitude': 175.6829, 'named': 'Hobbiton' }, "text": "In a hole in the ground there lived a hobbit. Not a nasty, dirty, wet hole, filled with the ends of worms and an oozy smell, nor yet a dry, bare, sandy hole with nothing in it to sit down on or to eat: it was a hobbit-hole, and that means comfort." } entry = Entry(**args) print(type(entry.location)) print(type(entry.creationDate))
注意事项
- 原代码中
creationDate字段的default_factory=dateutil.parser.parse写法有误:default_factory要求传入无参可调用对象,dateutil.parser.parse必须传入时间字符串参数,未传值调用时会直接抛出异常。如果需要给creationDate配置默认值(比如默认取当前时间),可以自定义无参函数作为工厂。 - 不需要额外编写类工厂方法做字段过滤,在
__post_init__中处理类型转换时同步过滤无效字段即可,代码更精简,完全符合dataclasses的设计初衷。 - 后续如果新增其他需要类型转换的字段,直接在
__post_init__中扩展对应逻辑即可,不需要修改上层实例化的调用代码。
内容的提问来源于stack exchange,提问作者jlconlin
相关产品推荐
相关产品推荐

