SQLALCHEMY中DeclarativeBase,MappedAsDataclass

简介: Python 中 SQLAlchemy 中这一开源库的使用方法

SQLAlchemy模型设计中的一些核心概念。这种设计(继承 MappedAsDataclassDeclarativeBase)是SQLAlchemy现代声明式模型的一种强大且类型安全的写法。

场景:

比如我们在flask中想要写一个Base 类去继承 MappedAsDataclass, DeclarativeBase,然后让其它的类去继承Base类:

下面是基础类的定义

from sqlalchemy.orm import DeclarativeBase,MappedAsDataclass

class TypeBase(MappedAsDataclass, DeclarativeBase):
    """
    This is for adding type, after all finished, rename to Base.
    """

   pass


为什么继承 MappedAsDataclassDeclarativeBase

这个 TypeBase类被设计为项目中所有数据模型(如 Account)的总基类。它结合了两个父类的功能,为子类提供了强大的能力:

基类

核心作用

带来的好处

DeclarativeBase

数据库映射:这是SQLAlchemy声明式系统的核心。它负责将Python类与数据库表关联起来(通过__tablename__),并管理所有映射的列、关系等。

提供了ORM功能,让你能用Python对象操作数据库表。

MappedAsDataclass

数据类行为:这是一个混入类(Mixin),它让模型类具备类似Python dataclass的特性,如自动生成__init__方法。

简化了对象初始化,支持更清晰、更现代的类型注解(如Mapped[str]),使代码更易写、易读,且IDE支持更好。

简单来说,DeclarativeBase负责“数据库的事”,而 MappedAsDataclass负责“Python对象的事”。两者结合,创造出了一个既强大又易用的模型基类。

关系总结

组件 作用 与 mapped_column 的关系
DeclarativeBase 提供 ORM 基础设施 扫描和处理 mapped_column 返回的对象
MappedAsDataclass 提供 dataclass 功能 与 mapped_columninit 参数配合
mapped_column 创建列定义对 返回 MappedColumn,被 DeclarativeBase 处理
Registry 管理所有映射类 存储 Account 的映射信息
MetaData 管理数据库模式 存储 Account 的表定义


继承DeclarativeBase 的完整调用解析

执行 Python 文件(account.py)在下面的举例中有提到Account 类的定义继承了TypeBase类
    
from models.base import TypeBase
    
1. 加载 models/base.py 模块
    
2. 执行 base.py 顶层代码(按顺序)
    
   a. from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, ...
    
   b. 加载 sqlalchemy.orm 模块
      
      ┌─────────────────────────────────────
        sqlalchemy.orm 模块中:           
                                           
       1. 定义元类 DeclarativeAttributeIntercept
                                           
       2. 定义 DeclarativeBase           
          class DeclarativeBase(          
              metaclass=DeclarativeAttributeIntercept 
          ):                              
              # 类体执行                  
              # __init_subclass__ 被定义  
                                           
       3. 定义 MappedAsDataclass        
                                           
       4. 定义其他类(Mapped, mapped_column等) 
      └─────────────────────────────────────
    
   c. 回到 base.py 继续执行
      metadata = sa.MetaData()
    
   d. 定义 TypeBase 
      class TypeBase(MappedAsDataclass, DeclarativeBase):
          metadata = metadata
      
      执行 TypeBase 类体:metadata = metadata
      
      元类处理 TypeBase 类(DeclarativeAttributeIntercept)
      
      调用 __init_subclass__ 
      
      MappedAsDataclass.__init_subclass__
      
      DeclarativeBase.__init_subclass__
        (TypeBase 直接继承,执行 _setup_declarative_base)
      
      TypeBase 类定义完成,拥有 registry  metadata
    
3. TypeBase 导入完成,回到 account.py
    
4. 继续执行 account.py 的其他导入
    from sqlalchemy.orm import Mapped, mapped_column
    from sqlalchemy import String
    
5. 定义 Account 
    class Account(UserMixin, TypeBase):
        __tablename__ = "accounts"
        id: Mapped[str] = mapped_column(StringUUID, ...)
        name: Mapped[str] = mapped_column(String(255))
        email: Mapped[str] = mapped_column(String(255))
        password: Mapped[str | None] = mapped_column(...)
        password_salt: Mapped[str | None] = mapped_column(...)
        avatar: Mapped[str] = mapped_column(...)
    
6. 执行 Account 类体(按顺序执行字段定义)
    
7. 元类处理 Account 类(同样使用 DeclarativeAttributeIntercept)
    
8. 调用 __init_subclass__ 
    
    MappedAsDataclass.__init_subclass__(Account)
    
    DeclarativeBase.__init_subclass__(Account)
        (Account 间接继承,执行 _as_declarative)
        
        扫描 Account.__dict__,创建 Table  Mapper
        
        Account 类定义完成,拥有 __table__  __mapper__
        
9. Account 可以使用 ORM 功能


🔍 Account 类继承详细解释

下面是在Flask框架中使用Sqlalchemy定义的Account 类.

#engine.py
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData

POSTGRES_INDEXES_NAMING_CONVENTION:dict[str,str] = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(column_0_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

metadata = MetaData(naming_convention=POSTGRES_INDEXES_NAMING_CONVENTION)

db = SQLAlchemy(metadata=metadata)
--------------------------------------------------------------------------
#base.py
from sqlalchemy.orm import DeclarativeBase,MappedAsDataclass
from models.engine import metadata

class TypeBase(MappedAsDataclass, DeclarativeBase):
    """
    This is for adding type, after all finished, rename to Base.
    """

    metadata = metadata
    
--------------------------------------------------------------------------   
 #Account.py
    class Account(UserMixin, TypeBase):
    __tablename__ = "accounts"
    __table_args__ = (sa.PrimaryKeyConstraint("id", name="account_pkey"), sa.Index("account_email_idx", "email"))

    id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"), init=False)
    name: Mapped[str] = mapped_column(String(255))
    email: Mapped[str] = mapped_column(String(255))
    password: Mapped[str | None] = mapped_column(String(255), default=None)



⚖️ 对比:不继承父类的传统写法

如果不使用 TypeBase提供的现代组合功能,Account类的定义会变得冗长且缺乏类型提示。

下面是一个简化的对比示例,假设只定义 id, name, email三个字段:

# ✅ 现代写法(继承 TypeBase)
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
from sqlalchemy import String, func

class TypeBase(MappedAsDataclass, DeclarativeBase):
    pass

class Account(TypeBase):
    __tablename__ = "accounts"
    id: Mapped[str] = mapped_column(primary_key=True, init=False)
    name: Mapped[str] = mapped_column(String(255))
    email: Mapped[str] = mapped_column(String(255))

# 创建对象非常简洁,且有类型检查
new_account = Account(name="张三", email="zhangsan@example.com")

----------------------------------------------------------------------------------------------
# ❌ 传统写法(不继承这些父类)
from sqlalchemy import Column, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 1. 需要手动创建基类
Base = declarative_base()

# 2. 类定义无法使用 Mapped 类型注解,列定义用 Column
class Account(Base):
    __tablename__ = "accounts"
    # 每个字段都需要用 Column 详细定义,代码冗长
    id = Column(String(255), primary_key=True)
    name = Column(String(255))
    email = Column(String(255))

    # 3. 如果想有初始化逻辑,需手动写 __init__
    def __init__(self, name, email):
        self.name = name
        self.email = email

    # 4. 没有方便的默认 __repr__ 等方法

# 创建对象
new_account = Account(name="张三", email="zhangsan@example.com")

💡 不继承 DeclarativeBase

如果不继承 DeclarativeBase,你将无法使用SQLAlchemy的声明式模型(即通过类定义表结构这种最常用的方式),而必须回归到更底层的命令式映射(Imperative Mapping 或 Classical Mapping)。

这意味着你需要:

  1. 先手动定义一个 Table对象来描述表结构。
  2. 再定义一个普通的Python类。
  3. 最后使用 mapper()函数手动地将两者关联起来
# ✅ 现代写法:继承 DeclarativeBase
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    pass

class User(Base):  # 继承DeclarativeBase
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column()
       
--------------------------------------------------------------------------------------
    
# ❌ 传统写法:不继承 DeclarativeBase (命令式映射)
from sqlalchemy import Table, Column, Integer, String, MetaData
from sqlalchemy.orm import registry

# 1. 手动创建注册表
mapper_registry = registry()
metadata = MetaData()

# 2. 先像定义普通SQL表一样,用Table构造表
user_table = Table(
    "users",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(30)),
)

# 3. 定义一个普通的Python类
class User:
    def __init__(self, name: str):
        self.name = name

# 4. 使用注册表的 map_imperatively 方法手动将类和表关联起来
mapper_registry.map_imperatively(User, user_table)

主要影响:代码变得冗长且不直观,失去了声明式模型的简洁性和可读性。声明式模型将所有信息集中在类定义中,一目了然


💡 不继承 MappedAsDataclass

MappedAsDataclass是一个混入类,它为你提供了类似Python dataclass的便利特性。如果不继承它,你将无法使用SQLAlchemy 2.0风格的现代类型注解,也无法自动获得一些便利方法。

# ✅ 现代写法:继承 MappedAsDataclass
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
class Base(MappedAsDataclass, DeclarativeBase):
    pass
class User(Base):  # 继承的Base包含了MappedAsDataclass
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True, init=False)  # 使用Mapped注解,init=False控制构造函数
    name: Mapped[str] = mapped_column()
    email: Mapped[str | None] = mapped_column(default=None)  # 支持默认值
# 创建对象非常简洁,且有类型检查和自动生成的__init__方法
new_user = User(name="张三")  # 只需传name,email使用默认值None


# ❌ 传统写法:不继承 MappedAsDataclass (仅继承DeclarativeBase)
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
    pass
class User(Base):  # 仅继承DeclarativeBase
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)  # 不使用Mapped注解
    name = Column(String(30))
    email = Column(String(255), default=None)  # 数据库层面的默认值
    # 可能需要手动编写__init__方法以获得灵活的构造函数
    def __init__(self, name: str, email: str | None = None):
        self.name = name
        self.email = email
# 创建对象
new_user = User(name="李四")

主要影响

  1. 失去现代类型注解:无法使用 Mapped[类型]这种清晰的注解方式,代码的类型提示作用减弱,IDE的智能提示和支持也会变差。
  2. 失去数据类特性:不会自动生成一个智能的 __init__构造函数。你需要手动编写 __init__方法才能实现类似 init=False(某些字段不放入构造函数)或灵活的默认值逻辑


总结

可以这样理解这个设计:

  • TypeBase:是项目自产的、功能强大的 “模型零件”,它融合了数据库映射和现代化数据类两种特性。
  • Account:作为具体的 “产品”(用户账户模型),使用统一的“零件”是天经地义、也是最可靠的选择。


目录
相关文章
|
7月前
|
存储 关系型数据库 数据库
Flask中定义类使用Sqlalchemy时,mapped_column详细讲解
本文详解 SQLAlchemy 2.0 中 `mapped_column` 的用法,介绍其语法结构、常用参数(如 `nullable`、`default`、`server_default`、`onupdate` 等),并通过 DIFY 的 Account 模型实例解析字段定义逻辑,对比数据库列与运行时字段的区别,帮助理解 ORM 映射机制。
662 3
|
数据库 UED Python
1、基于python多进程+pyqt5开发流畅界面程序
使用python+pyqt5开发界面程序,利用多进程分离界面和任务执行功能,达到界面流畅不卡顿的要求。 本文程序示例:https://github.com/AlvinsFish/UiExample
3019 0
1、基于python多进程+pyqt5开发流畅界面程序
|
存储 开发工具 Docker
Docker数据目录迁移解决方案
在docker的使用中随着下载镜像越来越多,构建镜像、运行容器越来越多, 数据目录必然会逐渐增大;当所有docker镜像、容器对磁盘的使用达到上限时,就需要对数据目录进行迁移。
14535 1
|
7月前
|
存储 自然语言处理 数据库
RAG(检索增强生成)技术简介
RAG(检索增强生成)通过结合信息检索与文本生成,提升大模型回答的准确性与时效性。它利用向量数据库实现语义检索,支持智能客服、医疗诊断、法律咨询等场景,解决知识更新难、专业性不足等问题,并以“按需取用”机制突破上下文限制,兼顾效率与隐私。
1091 4
|
人工智能 JSON 安全
VIN码查询_标准版API:帮助解锁车辆的“身份证”详细信息的实战指南
VIN码(车辆识别号码)是由17位字母和数字组成的全球唯一编码,相当于汽车的“身份证”。通过解析VIN码,可获取品牌、车系、生产年份等关键信息。探数API平台的VIN码查询API(标准版),只需输入VIN码即可返回完整车辆配置信息。 该API适用于多种场景:电商平台可自动填充商品详情,提升准确性;维修行业能精准匹配零件与诊断需求;二手车市场则增强交易透明度与安全性。其调用流程简单,包括准备VIN码、构造请求、处理响应及异常处理。 VIN码不仅是查询工具,更是连接制造、销售、维修、保险等环节的纽带。
1266 6
|
SQL 关系型数据库 MySQL
SQLAlchemy + 协程,实现异步的 ORM
SQLAlchemy + 协程,实现异步的 ORM
1604 1
Mac每次都要执行source ~/.bash_profile配置的环境变量才生效
Mac每次都要执行source ~/.bash_profile配置的环境变量才生效
1347 2
|
SQL 存储 Go
【译】SQLAlchemy文档:SQLAlchemy 统一教程
【译】SQLAlchemy文档:SQLAlchemy 统一教程
729 1
|
SQL 测试技术 数据库
Flask与SQLAlchemy的神秘力量:如何让你的数据库飞起来?
【8月更文挑战第31天】在现代Web开发中,Flask和SQLAlchemy是热门技术栈,前者是轻量级Web框架,后者为强大的ORM库。本文介绍如何在Flask项目中集成SQLAlchemy,通过示例展示数据库操作方法,并分享最佳实践,如熟悉ORM、编写测试及适度使用SQL语句,以提升开发效率和代码质量。
770 0

热门文章

最新文章