递归实现:Python 字典、SQLAlchemy、Pydantic、Dataclass 互转工具

1,051 阅读6分钟

在实际开发中,我们经常需要在不同的数据结构(如字典、SQLAlchemy 模型、Pydantic 模型、Dataclass 等)之间进行转换。为了减少重复代码并提高转换的灵活性,设计一套通用的转换工具让代码更简洁。本文将通过两个核心函数实现数据对象与模型的双向转换。


一、为什么需要数据结构间的互转?

  • 统一数据操作:不同的场景中使用的结构可能不同,但处理逻辑需要一致。

  • API 数据通常以字典形式传递。

  • 数据库查询返回的是 SQLAlchemy 模型。

  • PydanticDataclass 是开发中常用的强类型模型。

  • 减少手动转换:手动编写转换逻辑容易出错且效率低。


二、核心工具概览

本文将讲解两个核心函数:

  1. data_to_model:将各种数据对象(字典、SQLAlchemy、Pydantic、Dataclass 等)转换为目标模型。

  2. model_to_data:将各种模型对象(SQLAlchemy、Pydantic、Dataclass 等)还原为原始数据(字典或列表)。


三、从数据对象到模型:data_to_model

函数功能

data_to_model 可以将输入数据(字典、模型对象等)转换为目标模型(Pydantic、SQLAlchemy 或 Dataclass)。支持单个对象和列表对象的递归转换。

核心代码

@classmethod
def data_to_model(
    cls,
    data_obj: Union[
        dict,
        BaseOrmTable,
        BaseModel,
        dataclass,
        List[dict],
        List[BaseOrmTable],
        List[BaseModel],
    ],
    to_model: Type[Union[BaseModel, BaseOrmTable, dataclass]],
) -> Union[BaseModel, List[BaseModel], List[BaseOrmTable], List[dataclass], None]:
    """
    将数据对象转换成 pydantic 或 sqlalchemy 模型对象, 如果是数据库库表模型对象则调用to_dict()后递归
    Args:
        data_obj: 支持 字典对象, pydantic、sqlalchemy模型对象, 列表对象
        to_model: 转换后数据模型

    Notes:
        - 对于实现了 to_dict() 方法的模型对象,将调用该方法返回字典。

    returns:
        转换后的对象
    """

    if isinstance(data_obj, dict):
        # 字典处理
        return to_model(**data_obj)

    elif isinstance(data_obj, BaseOrmTable):
        # 数据库表模型对象处理, to_dict()后递归调用
        return cls.data_to_model(data_obj.to_dict(), to_model=to_model)

    elif isinstance(data_obj, BaseModel):
        # pydantic v2 模型对象处理, model_dump 后递归调用
        return cls.data_to_model(data_obj.model_dump(), to_model=to_model)

    elif dataclasses.is_dataclass(data_obj):
        # dataclass 模型对象处理, asdict() 后递归调用
        return cls.data_to_model(asdict(data_obj), to_model=to_model)

    elif hasattr(data_obj, "to_dict"):
        # 如果模型对象有 to_dict 方法,调用该方法返回字典
        return cls.data_to_model(data_obj.to_dict(), to_model=to_model)

    elif isinstance(data_obj, list):
        # 列表处理
        return [cls.data_to_model(item, to_model=to_model) for item in data_obj]

    else:
        raise ValueError(f"不支持此{data_obj}类型的序列化转换")

转换流程

  1. 字典处理:直接解包为目标模型。

  2. SQLAlchemy 模型处理:调用 to_dict 方法后递归处理。

  3. Pydantic 模型处理:使用 model_dump 方法生成字典后递归处理。

  4. Dataclass 处理:通过 asdict 方法生成字典后递归处理。

  5. 列表处理:逐个递归处理列表中的元素。

  6. 其他实现了 to_dict 方法的调用其 to_dict 递归处理

使用示例

先定义一些 pydantic、dataclass 等模型对象

from dataclasses import dataclass

from pydantic import BaseModel
from sqlalchemy import Column, String

from py_tools.connections.db.mysql import BaseOrmTable
from py_tools.utils import SerializerUtil


# sqlalchemy 示例
class UserTable(BaseOrmTable):
    __tablename__ = "user"
    username = Column(String(20))
    email = Column(String(50))


# Pydantic 示例
class UserModel(BaseModel):
    id: int
    username: str
    email: str


@dataclass
class UserDataclass:
    id: int
    username: str
    email: str


class UserCustomModel:
    def __init__(self, id: int, username: str, email: str):
        self.id = id
        self.username = username
        self.email = email

    def to_dict(self):
        return {"id": self.id, "username": self.username, "email": self.email}

数据转换demo


def data_to_model_demo():
    user_table_obj = UserTable(id=2, username="wang", email="wang@example.com")
    user_model_obj = UserModel(id=3, username="zack", email="zack@example.com")
    user_dataclass_obj = UserDataclass(id=4, username="lisa", email="lisa@example.com")
    user_custom_model = UserCustomModel(id=5, username="lily", email="lily@example.com")
    user_infos = [
        {"id": 1, "username": "hui", "email": "hui@example.com"},
        user_table_obj,
        user_model_obj,
        user_dataclass_obj,
        user_custom_model,
    ]

    print("data_to_model")
    user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserModel)
    print(type(user_models), user_models)

    user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserTable)
    print(type(user_models), user_models)

    user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserDataclass)
    print(type(user_models), user_models)

    user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserCustomModel)
    print(type(user_models), user_models)

    user_model = SerializerUtil.data_to_model(data_obj=user_infos[0], to_model=UserModel)
    user_table = SerializerUtil.data_to_model(data_obj=user_infos[0], to_model=UserTable)
    user_dataclass = SerializerUtil.data_to_model(data_obj=user_infos[0], to_model=UserDataclass)
    print(type(user_model), user_model)
    print(type(user_table), user_table)
    print(type(user_dataclass), user_dataclass)

转换效果如下

四、从模型到数据对象:model_to_data

函数功能

model_to_data 将模型对象(SQLAlchemy、Pydantic 或 Dataclass)转换为字典或列表,适用于需要原始数据结构的场景。

核心代码

@classmethod
def model_to_data(
    cls,
    model_obj: Union[
        BaseModel,
        BaseOrmTable,
        dataclass,
        List[BaseModel],
        List[BaseOrmTable],
        List[dataclass],
    ],
) -> Union[dict, List[dict], None]:
    """
    将 Pydantic 模型或 SQLAlchemy 模型对象转换回原始字典或列表对象。

    Args:
        model_obj: 支持 Pydantic 模型对象、SQLAlchemy 模型、dataclass 对象,或者它们的列表

    Notes:
        - 对于实现了 to_dict() 方法的模型对象,将调用该方法返回字典。

    Returns:
        转换后的字典或列表
    """

    if isinstance(model_obj, dict):
        return model_obj

    if isinstance(model_obj, RowMapping):
        return dict(model_obj)

    elif isinstance(model_obj, BaseModel):
        # Pydantic 模型对象处理,model_dump() 返回字典
        return model_obj.model_dump()

    elif isinstance(model_obj, BaseOrmTable):
        # SQLAlchemy 模型对象处理,to_dict() 返回字典
        return model_obj.to_dict()

    elif dataclasses.is_dataclass(model_obj):
        # dataclass 模型对象处理, asdict() 返回字典
        return asdict(model_obj)

    elif hasattr(model_obj, "to_dict"):
        # 如果模型对象有 to_dict 方法,调用该方法返回字典
        return model_obj.to_dict()

    elif isinstance(model_obj, list):
        # 列表处理,递归转换每个元素
        return [cls.model_to_data(item) for item in model_obj]

    else:
        raise ValueError(f"不支持此{model_obj}类型的反序列化转换")

转换流程

  1. Pydantic 模型:使用 model_dump 方法生成字典。

  2. SQLAlchemy 模型:通过 to_dict 方法生成字典。

  3. Dataclass:通过 asdict 方法生成字典。

  4. 列表处理:递归处理每个元素。

  5. 其他实现了 to_dict 方法的调用其 to_dict 生成字典

使用示例

def model_to_data_demo():
    user_table_obj = UserTable(id=2, username="wang", email="wang@example.com")
    user_model_obj = UserModel(id=3, username="zack", email="zack@example.com")
    user_dataclass_obj = UserDataclass(id=4, username="lisa", email="lisa@example.com")
    user_custom_model = UserCustomModel(id=5, username="lily", email="lily@example.com")
    user_infos = [
        {"id": 1, "username": "hui", "email": "hui@example.com"},
        user_table_obj,
        user_model_obj,
        user_dataclass_obj,
        user_custom_model,
    ]

    # model_to_data
    print("model_to_data")
    user_infos = SerializerUtil.model_to_data(user_infos)
    print(type(user_infos), user_infos)

    user_info = SerializerUtil.model_to_data(user_model_obj)
    print(type(user_info), user_info)

    user_info = SerializerUtil.model_to_data(user_table_obj)
    print(type(user_info), user_info)

    user_info = SerializerUtil.model_to_data(user_dataclass_obj)
    print(type(user_info), user_info)

    user_info = SerializerUtil.model_to_data(user_custom_model)
    print(type(user_info), user_info)

转换效果如下:

五、总结

  • data_to_modelmodel_to_data 提供了统一的数据转换能力,能够在字典、SQLAlchemy 模型、Pydantic 模型和 Dataclass 之间灵活转换。

  • 在 Web 开发中,这两个函数可以用于 API 数据解析、数据库模型转换、响应数据格式化等多个场景。

通过这些工具,开发者可以显著提高代码的复用性和可维护性,同时让代码更清晰、简洁

六、Github源代码

HuiDBK/py-tools: 打造 Python 开发常用的工具,让Coding变得更简单 (github.com)

模型对象互转工具的封装已集成到 hui-tools 中,安装 hui-tools 后可以直接使用。

示例代码:github.com/HuiDBK/py-t…