使用Pydantic校验Hydra-core ListConfig类型参数的解决方案
问题背景
使用pydantic校验hydra解析的YAML列表参数时,hydra生成的配置中列表参数并非原生Python list类型,而是omegaconf.listconfig.ListConfig类实例,直接传入pydantic模型会触发校验失败。
复现代码
项目共2个核心文件:
- 配置文件
cfg.yaml
params_list: - 10 - 0 - 20
- 解析校验文件
main.py
import hydra import pydantic from omegaconf import DictConfig from typing import List class Test(pydantic.BaseModel): params_list: List[int] @hydra.main(config_path=".", config_name="cfg.yaml") def go(cfg: DictConfig): parsed_cfg = Test(**cfg) print(parsed_cfg) if __name__ == "__main__": go()
报错现象
执行python3 main.py时抛出如下错误:
value is not a valid list (type=type_error.list)
在go()函数内打印type(cfg['params_list'])可确认参数类型为omegaconf.listconfig.ListConfig,而非pydantic预期的原生list类型。
复现步骤
- 在同一目录下创建上述
cfg.yaml与main.py文件 - 创建
requirements.txt写入依赖pydantic、hydra-core,安装依赖并激活虚拟环境 - 执行
python3 main.py即可复现报错
最简实现方案
方案1:全局转换(推荐,代码量最少)
不需要修改pydantic模型定义,在传入模型前调用OmegaConf自带的to_container方法,将所有OmegaConf自定义容器批量转为原生Python类型,同时支持自动解析hydra的插值引用:
import hydra import pydantic from omegaconf import DictConfig, OmegaConf from typing import List class Test(pydantic.BaseModel): params_list: List[int] @hydra.main(config_path=".", config_name="cfg.yaml") def go(cfg: DictConfig): # 转为原生嵌套类型,resolve=True表示解析所有配置插值 native_cfg = OmegaConf.to_container(cfg, resolve=True) parsed_cfg = Test(**native_cfg) print(parsed_cfg) if __name__ == "__main__": go()
该方案适配所有OmegaConf自定义类型,包括嵌套的ListConfig、DictConfig,不需要为单个字段单独写校验逻辑,适合配置项较多的场景。
方案2:字段级前置校验器
如果仅需要适配个别列表字段,可通过pydantic的前置校验器实现,字段类型标注保持原有List[int]即可,注意必须给校验器加pre=True参数,让转换逻辑在pydantic默认类型校验前执行:
import hydra import pydantic from omegaconf import DictConfig, ListConfig from typing import Any, List class Test(pydantic.BaseModel): params_list: List[int] # pre=True表示该校验器在默认类型校验前运行 @pydantic.validator("params_list", pre=True) def convert_list_config(cls, v: Any): if isinstance(v, ListConfig): return list(v) return v @hydra.main(config_path=".", config_name="cfg.yaml") def go(cfg: DictConfig): parsed_cfg = Test(**cfg) print(parsed_cfg) if __name__ == "__main__": go()
校验器将ListConfig转为原生list后,pydantic会自动完成后续的元素类型校验(即校验列表内元素是否均为int),不需要额外写冗余的类型判断逻辑。
内容的提问来源于stack exchange,提问作者Leonardo

