使用pydantic-settings 2.4.0结合YAML配置文件时出现__fields_set__属性错误,求正确配置方案
兄弟,我来帮你搞定这个问题!你遇到的AttributeError: 'ServiceSettingsYaml' object has no attribute '__fields_set__',本质是因为你绕开了Pydantic BaseSettings的原生初始化逻辑,导致内部必要属性没被创建。下面给你分析原因+正确的配置方案:
问题根源
你继承了pydantic.v1.BaseSettings但重写了__init__方法,却没有调用父类的__init__()。Pydantic的BaseSettings在初始化时会自动创建__fields_set__这类内部管理属性,你跳过这一步直接给实例赋值,就会触发Pydantic的属性校验逻辑,而此时依赖的__fields_set__还不存在,自然报错。
而且更关键的是:Pydantic Settings本身就支持原生加载YAML配置,完全不需要你手动在__init__里读YAML再赋值,这属于重复造轮子还踩了坑。
正确配置方案(推荐,符合Pydantic设计)
因为你用的是pydantic-settings==2.4.0,建议直接用它的新版API,配合嵌套模型自动映射YAML结构,代码更简洁还不会出错。
步骤1:确保依赖齐全
先安装pyyaml(Pydantic需要它解析YAML文件):
pip install pyyaml
步骤2:编写配置类
用新版BaseSettings(来自pydantic_settings而非pydantic.v1),通过model_config指定YAML路径,用嵌套模型匹配YAML的层级结构:
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import AnyHttpUrl, Field from typing import List, Optional # 嵌套模型:对应YAML里的kafka_consumer节点结构 class KafkaConsumerSettings(BaseSettings): host: str port: int kafka_topics: str # 嵌套模型:对应YAML里的数据库配置节点 class DatabaseSettings(BaseSettings): user: Optional[str] = None password: Optional[str] = None host: Optional[str] = None port: Optional[str] = None name: Optional[str] = None sqlalchemy_database_url: Optional[str] = None class ServiceSettingsYaml(BaseSettings): backend_cors_origins: List[AnyHttpUrl] = [] kafka_consumer: KafkaConsumerSettings # 自动映射YAML的kafka_consumer节点 file_encoding: str = "utf-8" db: DatabaseSettings # 自动映射YAML的db节点 # 核心配置:指定YAML文件路径和编码 model_config = SettingsConfigDict( yaml_file="configs/dev-config.yaml", env_file_encoding="utf-8" ) # 直接实例化,Pydantic会自动加载并解析YAML settings = ServiceSettingsYaml() # 测试访问配置 print(settings.kafka_consumer.host) # 输出: kafka print(settings.kafka_consumer.port) # 输出: 9092 print(settings.db.host) # 输出YAML里的db.host值
对应的YAML文件示例(configs/dev-config.yaml)
kafka_consumer: host: kafka port: 9092 kafka_topics: my_topic file_encoding: utf-8 db: user: my_db_user password: my_db_pass host: db port: 5432 name: my_db_name
如果你坚持要用pydantic.v1的API(不推荐)
如果因为某些原因必须用pydantic.v1.BaseSettings,那一定要在__init__里先调用父类的初始化方法:
from pydantic.v1 import BaseSettings, AnyHttpUrl from typing import List, Optional import yaml def load_yaml_config(config_path: str): with open(config_path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) class ServiceSettingsYaml(BaseSettings): backend_cors_origins: List[AnyHttpUrl] = [] kafka_host: Optional[str] = None kafka_port: Optional[int] = None kafka_topics: Optional[str] = None file_encoding: str = "utf-8" # 其他数据库属性... def __init__(self, config_file_path: str = "configs/dev-config.yaml"): # 必须先调用父类__init__,初始化内部属性如__fields_set__ super().__init__() config = load_yaml_config(config_file_path) # 现在再赋值就不会报错了 self.kafka_host = config["kafka_consumer"]["host"] self.kafka_port = config["kafka_consumer"]["port"] self.kafka_topics = config["kafka_consumer"]["kafka_topics"] # 实例化测试 settings = ServiceSettingsYaml() print(settings.kafka_host) # 输出: kafka
为什么这个方案能解决问题?
第一种推荐方案完全遵循Pydantic Settings的设计,它会自动处理:
- YAML文件的加载和解析
- 类型校验(比如把YAML里的port自动转成int)
- 嵌套结构的映射
- 内部属性的正确初始化(包括
__fields_set__)
而第二种方案通过调用super().__init__()补全了父类的初始化流程,让Pydantic的内部属性正常创建,避免了报错。
备注:内容来源于stack exchange,提问作者mascai

