Skip to content

SQLAlchemy对象转字典

背景

已知一个模型对象,需要将其转成字典格式。

模型示例:

class User(Base):
    """用户模型Model"""

    # 定义表名称
    __tablename__ = 'user'

    # 定义表字段
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    age: Mapped[int] = mapped_column(Integer)

执行查询:

user = db_session.query(User).first()
print(user)

上述打印输出对是对象信息,例如<__main__.User object at 0x104d9e140>,展示结果不够直观,希望能将模型对象转成字典格式:把属性名作为字典键,把属性值作为字典值。

预期输出结果:

{'id': 1, 'name': '百里', 'age': 18}

实现

通过 __dict__

  • 思路

在 Python 中,可以通过Python内置函数dir()来查看一个对象的所有属性和方法名列表,包括所有的魔法属性和普通成员属性。

obj = get_user()
print(dir(obj))
输出结果:
['__abstract__', '__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__mapper__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__table__', '__tablename__', '__weakref__', '_sa_class_manager', '_sa_instance_state', '_sa_registry', 'age', 'as_dict', 'id', 'metadata', 'name', 'registry']

其中,每个对象都有一个 __dict__ 属性,它是一个字典,用于存储对象的所有属性和对应的值。 当我们访问一个对象的属性时,Python 解释器会先查找对象的 __dict__ 属性,然后在 __dict__ 中查找属性名对应的值。

执行代码:

obj = get_user()
print(obj.__dict__)

输出结果:

{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x104db0d60>, 'id': 1, 'name': '百里', 'age': 18}

可以看到,已经非常接近我们期望的字典。由于userSQLAlchemy的对象,所以其中会有一些其他的属性比如_sa_instance这种,过滤即可。

  • 实现

首先,在User类中添加as_dict()方法,返回对象的字典表示形式:

def as_dict(self):
    return {k: v for k, v in self.__dict__.items() if not k.starts_with('_')}

然后执行:

obj = get_user()
print(obj.as_dict())
输出结果:
{'id': 1, 'name': '百里', 'age': 18}

可以看到已经获得了对象的字典表示形式。

完整代码:

from sqlalchemy import create_engine, String, Integer
from sqlalchemy.orm import declarative_base, mapped_column, Mapped, Session

# 基类
Base = declarative_base()

# 引擎
engine = create_engine('sqlite:///test.db', echo=True)

class User(Base):
    """用户模型Model"""

    # 定义表名称
    __tablename__ = 'user'

    # 定义表字段
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    age: Mapped[int] = mapped_column(Integer)

    def as_dict(self):
        return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}

def __init_db():
    """初始化数据库"""

    # 建表
    Base.metadata.create_all(engine)

    # 实例化模型对象
    user = User(
        name='百里',
        age=18
    )

    # 插入数据库
    with Session(engine) as db_session:
        db_session.add(user)
        db_session.commit()


def get_user():

    # 查询用户表记录
    with Session(engine) as db_session:
        user = db_session.query(User).first()

    return user


if __name__ == '__main__':
    obj = get_user()
    # print(obj)
    print(obj.as_dict())

通过 __table__

__table__ 是 SQLAlchemy 中一个重要的属性,用于将模型对象与数据库表相对应。通过指定 __table__ 属性,可以在模型对象中描述表的各个方面,例如表名、列名、主键等。__table__ 属性通常与 SQLAlchemy 的 Table 对象一起使用,以提供对表的更多控制。

  • 思路

在 SQLAlchemy 中,每个 ORM 模型类都对应一个数据库表。__table__ 属性是 ORM 模型类中的一个特殊属性,用于指定模型类对应的数据库表的元信息,例如表名、列名、数据类型等。 因为表的字段名跟模型类对象的的属性名是相同的,因此可以考虑通过模型对应表的列信息,来间接组装成对象的属性和属性值字典。

obj = get_user()
print(obj.__table__.columns)

输出结果:

ReadOnlyColumnCollection(user.id, user.name, user.age)

  • 实现

首先,在User类中添加as_dict()方法,返回对象的字典表示形式:

def as_dict(self):
    return {c.name: getattr(self, c.name, None) for c in self.__table__.columns}

然后执行:

obj = get_user()
print(obj.as_dict())
输出结果:
{'id': 1, 'name': '百里', 'age': 18}

完整代码:

from sqlalchemy import create_engine, String, Integer
from sqlalchemy.orm import declarative_base, mapped_column, Mapped, Session

# 基类
Base = declarative_base()

# 引擎
engine = create_engine('sqlite:///test.db', echo=True)


class User(Base):
    """用户模型Model"""

    # 定义表名称
    __tablename__ = 'user'

    # 定义表字段
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    age: Mapped[int] = mapped_column(Integer)

    def as_dict(self):
        return {c.name: getattr(self, c.name, None) for c in self.__table__.columns}

def __init_db():
    """初始化数据库"""

    # 建表
    Base.metadata.create_all(engine)

    # 实例化模型对象
    user = User(
        name='百里',
        age=18
    )

    # 插入数据库
    with Session(engine) as db_session:
        db_session.add(user)
        db_session.commit()


def get_user():

    # 查询用户表记录
    with Session(engine) as db_session:
        user = db_session.query(User).first()

    return user


if __name__ == '__main__':
    obj = get_user()
    print(obj.as_dict())

通过 dataclass

  • 思路

dataclass 数据类是 Python 3.7 中新增的一个装饰器,用于简化创建和操作纯数据对象。通过使用 dataclass 装饰器,我们可以在不需要定义繁琐的 __init____repr____eq__ 等方法的情况下,快速创建一个数据类。

使用 dataclass 装饰器定义的类可以通过调用dataclasses本身提供的 asdict() 函数将实例转换成字典。这个函数将返回一个字典,其中包含了实例中的所有属性及其对应的值。

数据类示例:

from dataclasses import dataclass, asdict

@dataclass
class User:
    id: int
    name: str
    age: int

user = User(1, '百里', 18)
print(asdict(user))

输出结果:

{'id': 1, 'name': '百里', 'age': 18}

  • 实现

dataclasses导入dataclassasdict方法,然后在User模型类上使用@dataclass进行装饰。

from dataclasses import dataclass, asdict

@dataclass
class User(Base):
    """用户模型Model"""

    # 定义表名称
    __tablename__ = 'user'

    # 定义表字段
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    age: Mapped[int] = mapped_column(Integer)

这样一来,就可以直接调用数据类提供对as_dict(obj)方法,便捷地将模型类的对象转换成字典形式。

执行代码

obj = get_user()
print(asdict(obj))

输出结果:

{'id': 1, 'name': '百里', 'age': 18}

完整代码:

from sqlalchemy import create_engine, String, Integer
from sqlalchemy.orm import declarative_base, mapped_column, Mapped, Session
from dataclasses import dataclass, asdict

# 基类
Base = declarative_base()

# 引擎
engine = create_engine('sqlite:///test.db', echo=True)


@dataclass
class User(Base):
    """用户模型Model"""

    # 定义表名称
    __tablename__ = 'user'

    # 定义表字段
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    age: Mapped[int] = mapped_column(Integer)


def __init_db():
    """初始化数据库"""

    # 建表
    Base.metadata.create_all(engine)

    # 实例化模型对象
    user = User(
        name='百里',
        age=18
    )

    # 插入数据库
    with Session(engine) as db_session:
        db_session.add(user)
        db_session.commit()


def get_user():

    # 查询用户表记录
    with Session(engine) as db_session:
        user = db_session.query(User).first()

    return user


if __name__ == '__main__':
    obj = get_user()
    print(asdict(obj))

参考资料

  1. Python官方文档:__dict__
  2. Mapping Table Columns
  3. Python官方文档:Data Classes