SqlAlchemy 2.0 中文文档(四)(4)https://developer.aliyun.com/article/1562997
映射类行为
在使用registry
对象进行所有映射样式时,以下行为是共同的:
默认构造函数
registry
将默认构造函数,即__init__
方法,应用于所有没有明确自己的__init__
方法的映射类。此方法的行为是提供一个方便的关键字构造函数,将接受所有命名属性作为可选关键字参数。例如:
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] fullname: Mapped[str]
上面的User
类型对象将具有允许创建User
对象的构造函数,如下所示:
u1 = User(name="some name", fullname="some fullname")
提示
声明式数据类映射功能通过使用 Python 数据类提供了一种生成默认__init__()
方法的替代方法,并且允许高度可配置的构造函数形式。
警告
类的__init__()
方法仅在 Python 代码中构造对象时调用,而不是在从数据库加载或刷新对象时调用。请参阅下一节在加载过程中保持非映射状态,了解如何在加载对象时调用特殊逻辑的入门知识。
包含显式__init__()
方法的类将保留该方法,并且不会应用默认构造函数。
要更改所使用的默认构造函数,可以向registry.constructor
参数提供用户定义的 Python 可调用对象,该对象将用作默认构造函数。
构造函数也适用于命令式映射:
from sqlalchemy.orm import registry mapper_registry = registry() user_table = Table( "user", mapper_registry.metadata, Column("id", Integer, primary_key=True), Column("name", String(50)), ) class User: pass mapper_registry.map_imperatively(User, user_table)
如上所述,通过命令式映射描述的类也将具有与registry
相关联的默认构造函数。
新版 1.4 中:经典映射现在在通过registry.map_imperatively()
方法进行映射时支持标准配置级别的构造函数。### 在加载过程中保持非映射状态
映射类的__init__()
方法在 Python 代码中直接构造对象时被调用:
u1 = User(name="some name", fullname="some fullname")
然而,当使用 ORM Session
加载对象时,不会调用__init__()
方法:
u1 = session.scalars(select(User).where(User.name == "some name")).first()
这是因为从数据库加载时,用于构造对象的操作(在上面的示例中为User
)更类似于反序列化,如取消持久性,而不是初始构造。大多数对象的重要状态不是首次组装,而是从数据库行重新加载。
因此,为了在对象中维护不是数据库中存储的数据的状态,使得当对象被加载和构造时此状态存在,下面详细介绍了两种一般方法。
- 使用 Python 描述符(如
@property
),而不是状态,根据需要动态计算属性。
对于简单的属性,这是最简单且最不容易出错的方法。例如,如果一个名为Point
的对象希望具有这些属性的总和:
class Point(Base): __tablename__ = "point" id: Mapped[int] = mapped_column(primary_key=True) x: Mapped[int] y: Mapped[int] @property def x_plus_y(self): return self.x + self.y
- 使用动态描述符的优势在于,值每次都会重新计算,这意味着它会随着基础属性(在本例中为
x
和y
)可能会发生变化而保持正确的值。
上述模式的其他形式包括 Python 标准库的 cached_property 装饰器(它被缓存,而不是每次重新计算),以及 SQLAlchemy 的hybrid_property
装饰器,它允许属性既可用于 SQL 查询,也可用于 Python 属性。 - 使用
InstanceEvents.load()
建立加载时的状态,并且可选地使用补充方法InstanceEvents.refresh()
和InstanceEvents.refresh_flush()
。
这些是在从数据库加载对象或在对象过期后刷新时调用的事件钩子。通常只需要InstanceEvents.load()
,因为非映射的本地对象状态不受过期操作的影响。要修改上面的Point
示例,如下所示:
from sqlalchemy import event class Point(Base): __tablename__ = "point" id: Mapped[int] = mapped_column(primary_key=True) x: Mapped[int] y: Mapped[int] def __init__(self, x, y, **kw): super().__init__(x=x, y=y, **kw) self.x_plus_y = x + y @event.listens_for(Point, "load") def receive_load(target, context): target.x_plus_y = target.x + target.y
- 如果还使用刷新事件,事件钩子可以根据需要堆叠在一个可调用对象上,如下所示:
@event.listens_for(Point, "load") @event.listens_for(Point, "refresh") @event.listens_for(Point, "refresh_flush") def receive_load(target, context, attrs=None): target.x_plus_y = target.x + target.y
- 在上述情况下,
attrs
属性将出现在refresh
和refresh_flush
事件中,并指示正在刷新的属性名称列表。### 映射类、实例和映射器的运行时内省
使用 registry
映射的类还将包含一些对所有映射通用的属性:
__mapper__
属性将引用与类相关联的Mapper
:
mapper = User.__mapper__
- 当对映射类使用
inspect()
函数时,返回的也是此Mapper
:
from sqlalchemy import inspect mapper = inspect(User)
__table__
属性将引用类被映射到的Table
,或者更通用地引用类被映射到的FromClause
对象:
table = User.__table__
- 当使用
Mapper.local_table
属性时,返回的也是这个FromClause
:
table = inspect(User).local_table
- 对于单表继承映射,其中类是没有自己的表的子类,
Mapper.local_table
属性以及.__table__
属性将为None
。要检索在查询此类时实际选择的“可选择项”,可通过Mapper.selectable
属性获得:
table = inspect(User).selectable
Mapper 对象的检查
如前一节所示,Mapper
对象可从任何映射类获得,而不管方法如何,使用 Runtime Inspection API 系统。使用 inspect()
函数,可以从映射类获取 Mapper
:
>>> from sqlalchemy import inspect >>> insp = inspect(User)
可用的详细信息包括 Mapper.columns
:
>>> insp.columns <sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>
这是一个可以以列表格式或通过单个名称查看的命名空间:
>>> list(insp.columns) [Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)] >>> insp.columns.name Column('name', String(length=50), table=<user>)
其他命名空间包括 Mapper.all_orm_descriptors
,其中包括所有映射属性以及混合属性、关联代理:
>>> insp.all_orm_descriptors <sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68> >>> insp.all_orm_descriptors.keys() ['fullname', 'nickname', 'name', 'id']
以及 Mapper.column_attrs
:
>>> list(insp.column_attrs) [<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>] >>> insp.column_attrs.name <ColumnProperty at 0x10403fce8; name> >>> insp.column_attrs.name.expression Column('name', String(length=50), table=<user>)
另请参阅
Mapper
#### 映射实例的检查
inspect()
函数还提供关于映射类的实例的信息。当应用于映射类的实例时,而不是类本身时,返回的对象被称为 InstanceState
,它将提供链接,不仅链接到类使用的 Mapper
,还提供了一个详细的界面,提供了关于实例内部属性状态的信息,包括它们当前的值以及这与它们的数据库加载值有何关系。
给定从数据库加载的 User
类的实例:
>>> u1 = session.scalars(select(User)).first()
inspect()
函数将返回给我们一个 InstanceState
对象:
>>> insp = inspect(u1) >>> insp <sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>
通过该对象,我们可以查看诸如 Mapper
等元素:
>>> insp.mapper <Mapper at 0x7f07e614ef50; User>
对象所附属的 Session
(如果有):
>>> insp.session <sqlalchemy.orm.session.Session object at 0x7f07e614f160>
对象的当前持久状态的信息:
>>> insp.persistent True >>> insp.pending False
属性状态信息,例如未加载或延迟加载的属性(假设 addresses
是映射类到相关类的 relationship()
):
>>> insp.unloaded {'addresses'}
关于当前 Python 中属性的状态信息,例如自上次刷新以来未修改的属性:
>>> insp.unmodified {'nickname', 'name', 'fullname', 'id'}
以及自上次刷新以来对属性进行修改的具体历史:
>>> insp.attrs.nickname.value 'nickname' >>> u1.nickname = "new nickname" >>> insp.attrs.nickname.history History(added=['new nickname'], unchanged=(), deleted=['nickname'])
另请参阅
InstanceState
InstanceState.attrs
AttributeState
### 默认构造函数
registry
对所有未显式拥有自己 __init__
方法的映射类应用默认构造函数,即 __init__
方法。该方法的行为是提供一个方便的关键字构造函数,将接受所有命名属性作为可选关键字参数。例如:
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] fullname: Mapped[str]
上面的 User
类的对象将具有一个允许创建 User
对象的构造函数:
u1 = User(name="some name", fullname="some fullname")
提示
声明式数据类映射 功能通过使用 Python 数据类提供了一种生成默认 __init__()
方法的替代方式,并允许高度可配置的构造函数形式。
警告
当对象在 Python 代码中构造时才调用类的 __init__()
方法,而不是在从数据库加载或刷新对象时。请参阅下一节在加载时保持非映射状态,了解如何在加载对象时调用特殊逻辑的基本知识。
包含显式 __init__()
方法的类将保持该方法,不会应用默认构造函数。
若要更改使用的默认构造函数,可以提供用户定义的 Python 可调用对象给 registry.constructor
参数,该对象将用作默认构造函数。
构造函数也适用于命令式映射:
from sqlalchemy.orm import registry mapper_registry = registry() user_table = Table( "user", mapper_registry.metadata, Column("id", Integer, primary_key=True), Column("name", String(50)), ) class User: pass mapper_registry.map_imperatively(User, user_table)
如在命令式映射中所述,上述类将还具有与registry
相关联的默认构造函数。
新版本 1.4 中:经典映射现在支持通过registry.map_imperatively()
方法映射时的标准配置级构造函数。
在加载期间保持非映射状态
当直接在 Python 代码中构造对象时,会调用映射类的__init__()
方法:
u1 = User(name="some name", fullname="some fullname")
然而,当使用 ORM Session
加载对象时,不会调用__init__()
方法:
u1 = session.scalars(select(User).where(User.name == "some name")).first()
原因在于,当从数据库加载时,用于构造对象的操作,例如上面的User
,更类似于反序列化,例如取消选中,而不是初始构造。对象的大部分重要状态不是首次组装的,而是重新从数据库行加载的。
因此,为了在对象加载以及构造时保持对象中不是存储到数据库的数据的状态,以下详细介绍了两种一般方法。
- 使用 Python 描述符,如
@property
,而不是状态,根据需要动态计算属性。
对于简单属性,这是最简单且最少错误的方法。例如,如果具有Point.x
和Point.y
的对象Point
希望具有这些属性的和:
class Point(Base): __tablename__ = "point" id: Mapped[int] = mapped_column(primary_key=True) x: Mapped[int] y: Mapped[int] @property def x_plus_y(self): return self.x + self.y
- 使用动态描述符的优点是值每次计算,这意味着它保持正确的值,因为底层属性(在本例中为
x
和y
)可能会更改。
上述模式的其他形式包括 Python 标准库cached_property装饰器(它是缓存的,不会每次重新计算),以及 SQLAlchemy 的hybrid_property
装饰器,允许属性同时适用于 SQL 查询。 - 使用
InstanceEvents.load()
来在加载时建立状态,可选地使用补充方法InstanceEvents.refresh()
和InstanceEvents.refresh_flush()
。
这些是在对象从数据库加载时或在过期后刷新时调用的事件钩子。通常只需要InstanceEvents.load()
,因为非映射的本地对象状态不受到过期操作的影响。要修改上面的Point
示例,看起来像这样:
from sqlalchemy import event class Point(Base): __tablename__ = "point" id: Mapped[int] = mapped_column(primary_key=True) x: Mapped[int] y: Mapped[int] def __init__(self, x, y, **kw): super().__init__(x=x, y=y, **kw) self.x_plus_y = x + y @event.listens_for(Point, "load") def receive_load(target, context): target.x_plus_y = target.x + target.y
- 如果需要同时使用刷新事件,事件钩子可以叠加在一个可调用对象上,如下所示:
@event.listens_for(Point, "load") @event.listens_for(Point, "refresh") @event.listens_for(Point, "refresh_flush") def receive_load(target, context, attrs=None): target.x_plus_y = target.x + target.y
- 在上面的示例中,
attrs
属性将出现在refresh
和refresh_flush
事件中,并指示正在刷新的属性名称列表。
映射类、实例和映射器的运行时内省
使用 registry
进行映射的类还将具有一些所有映射的共同属性:
__mapper__
属性将引用与该类关联的Mapper
:
mapper = User.__mapper__
- 当使用
inspect()
函数对映射类进行检查时,也将返回此Mapper
:
from sqlalchemy import inspect mapper = inspect(User)
__table__
属性将引用将类映射到的Table
,或更一般地,引用FromClause
对象:
table = User.__table__
- 当使用
Mapper.local_table
属性时,此FromClause
也将返回:
table = inspect(User).local_table
- 对于单表继承映射,其中类是没有自己的表的子类,
Mapper.local_table
属性以及.__table__
属性都将为None
。要检索在查询此类时实际选择的“可选项”,可以通过Mapper.selectable
属性获取:
table = inspect(User).selectable
映射器对象的检查
如前一节所示,Mapper
对象可从任何映射类中使用 运行时内省 API 系统获取。使用 inspect()
函数,可以从映射类中获取 Mapper
:
>>> from sqlalchemy import inspect >>> insp = inspect(User)
可用的详细信息包括 Mapper.columns
:
>>> insp.columns <sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>
这是一个可以以列表格式或通过单个名称查看的命名空间:
>>> list(insp.columns) [Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)] >>> insp.columns.name Column('name', String(length=50), table=<user>)
其他命名空间包括 Mapper.all_orm_descriptors
,其中包括所有映射属性以及混合体,关联代理:
>>> insp.all_orm_descriptors <sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68> >>> insp.all_orm_descriptors.keys() ['fullname', 'nickname', 'name', 'id']
以及 Mapper.column_attrs
:
>>> list(insp.column_attrs) [<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>] >>> insp.column_attrs.name <ColumnProperty at 0x10403fce8; name> >>> insp.column_attrs.name.expression Column('name', String(length=50), table=<user>)
另请参阅
Mapper
#### 映射实例的检查
inspect()
函数还提供了关于映射类的实例的信息。当应用于映射类的实例而不是类本身时,返回的对象被称为 InstanceState
,它将提供链接到不仅由该类使用的 Mapper
,还提供了有关实例内部属性状态的详细接口的信息,包括它们的当前值以及这与它们的数据库加载值的关系。
给定从数据库加载的 User
类的实例:
>>> u1 = session.scalars(select(User)).first()
inspect()
函数将返回一个 InstanceState
对象:
>>> insp = inspect(u1) >>> insp <sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>
使用此对象,我们可以查看诸如 Mapper
之类的元素:
>>> insp.mapper <Mapper at 0x7f07e614ef50; User>
对象所附加到的 Session
(如果有):
>>> insp.session <sqlalchemy.orm.session.Session object at 0x7f07e614f160>
关于对象当前的持久性状态的信息:
>>> insp.persistent True >>> insp.pending False
属性状态信息,例如尚未加载或延迟加载的属性(假设 addresses
指的是映射类上的 relationship()
到相关类):
>>> insp.unloaded {'addresses'}
有关属性的当前 Python 内部状态的信息,例如自上次刷新以来未被修改的属性:
>>> insp.unmodified {'nickname', 'name', 'fullname', 'id'}
以及自上次刷新以来对属性进行的修改的特定历史记录:
>>> insp.attrs.nickname.value 'nickname' >>> u1.nickname = "new nickname" >>> insp.attrs.nickname.history History(added=['new nickname'], unchanged=(), deleted=['nickname'])
另请参阅
InstanceState
InstanceState.attrs
AttributeState
#### 映射对象的检查
如前一节所示,Mapper
对象可以从任何映射类中使用,无论方法如何,都可以使用 Runtime Inspection API 系统。使用inspect()
函数,可以从映射类获取Mapper
:
>>> from sqlalchemy import inspect >>> insp = inspect(User)
可用的详细信息包括Mapper.columns
:
>>> insp.columns <sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>
这是一个可以以列表格式或通过单个名称查看的命名空间:
>>> list(insp.columns) [Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)] >>> insp.columns.name Column('name', String(length=50), table=<user>)
其他命名空间包括Mapper.all_orm_descriptors
,其中包括所有映射属性以及混合属性,关联代理:
>>> insp.all_orm_descriptors <sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68> >>> insp.all_orm_descriptors.keys() ['fullname', 'nickname', 'name', 'id']
以及Mapper.column_attrs
:
>>> list(insp.column_attrs) [<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>] >>> insp.column_attrs.name <ColumnProperty at 0x10403fce8; name> >>> insp.column_attrs.name.expression Column('name', String(length=50), table=<user>)
另请参阅
Mapper
映射实例的检查
inspect()
函数还提供关于映射类的实例的信息。当应用于映射类的实例而不是类本身时,返回的对象称为InstanceState
,它将提供指向类使用的Mapper
的链接,以及提供有关实例内部属性状态的详细接口,包括它们当前的值以及这与它们的数据库加载值的关系。
给定从数据库加载的User
类的实例:
>>> u1 = session.scalars(select(User)).first()
inspect()
函数将向我们返回一个InstanceState
对象:
>>> insp = inspect(u1) >>> insp <sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>
使用此对象,我们可以查看诸如Mapper
之类的元素:
>>> insp.mapper <Mapper at 0x7f07e614ef50; User>
对象附加到的Session
(如果有):
>>> insp.session <sqlalchemy.orm.session.Session object at 0x7f07e614f160>
对象的当前 persistence state 的信息:
>>> insp.persistent True >>> insp.pending False
属性状态信息,如未加载或延迟加载的属性(假设addresses
指的是映射类上与相关类的relationship()
):
>>> insp.unloaded {'addresses'}
关于属性的当前 Python 状态的信息,例如自上次刷新以来未被修改的属性:
>>> insp.unmodified {'nickname', 'name', 'fullname', 'id'}
以及自上次刷新以来属性修改的具体历史:
>>> insp.attrs.nickname.value 'nickname' >>> u1.nickname = "new nickname" >>> insp.attrs.nickname.history History(added=['new nickname'], unchanged=(), deleted=['nickname'])
同样参见
InstanceState
InstanceState.attrs
AttributeState