原文:
docs.sqlalchemy.org/en/20/contents.html
声明式扩展
原文:
docs.sqlalchemy.org/en/20/orm/extensions/declarative/index.html
声明式映射 API 特定的扩展。
1.4 版本更改:绝大部分声明式扩展现在已整合到 SQLAlchemy ORM 中,并可从 sqlalchemy.orm
命名空间导入。请参阅声明式映射的文档以获取新文档。有关更改的概述,请参阅声明式现已与 ORM 整合,并带有新功能。
对象名称 | 描述 |
---|---|
AbstractConcreteBase | 一个用于“具体”声明式映射的辅助类。 |
ConcreteBase | 一个用于“具体”声明式映射的辅助类。 |
DeferredReflection | 一个用于基于延迟反射步骤构建映射的辅助类。 |
class sqlalchemy.ext.declarative.AbstractConcreteBase
一个用于“具体”声明式映射的辅助类。
AbstractConcreteBase
将自动使用 polymorphic_union()
函数,对所有作为此类的子类映射的表执行。该函数通过 __declare_first__()
函数调用,这实际上是一个 before_configured()
事件的钩子。
AbstractConcreteBase
应用 Mapper
到其直接继承的类,就像对任何其他声明式映射的类一样。然而,Mapper
没有映射到任何特定的 Table
对象。相反,它直接映射到由 polymorphic_union()
产生的“多态”可选择的对象,并且不执行自己的持久化操作。与 ConcreteBase
相比,后者将其直接继承的类映射到直接存储行的实际 Table
。
注意
AbstractConcreteBase
延迟了基类的映射器创建,直到所有子类都已定义,因为它需要创建一个针对包含所有子类表的可选择项的映射。为了实现这一点,它等待映射器配置事件发生,然后扫描所有配置的子类,并设置一个将一次性查询所有子类的映射。
虽然此事件通常会自动调用,但在AbstractConcreteBase
的情况下,如果第一个操作是针对此基类的查询,则可能需要在定义所有子类映射之后显式调用它。为此,一旦所有期望的类都已配置,可以调用正在使用的registry
上的registry.configure()
方法,该方法可在特定声明基类的关系中使用:
Base.registry.configure()
示例:
代码语言:javascript复制from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.ext.declarative import AbstractConcreteBase
class Base(DeclarativeBase):
pass
class Employee(AbstractConcreteBase, Base):
pass
class Manager(Employee):
__tablename__ = 'manager'
employee_id = Column(Integer, primary_key=True)
name = Column(String(50))
manager_data = Column(String(40))
__mapper_args__ = {
'polymorphic_identity':'manager',
'concrete':True
}
Base.registry.configure()
抽象基类在声明时以一种特殊的方式处理;在类配置时,它的行为类似于声明式的混入或__abstract__
基类。一旦类被配置并生成映射,它会被映射自身,但在其所有子类之后。这是在任何其他 SQLAlchemy API 功能中都找不到的非常独特的映射系统。
使用这种方法,我们可以指定将在映射的子类上发生的列和属性,就像我们通常在 Mixin 和自定义基类中所做的那样:
代码语言:javascript复制from sqlalchemy.ext.declarative import AbstractConcreteBase
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
class Employee(AbstractConcreteBase, Base):
strict_attrs = True
employee_id = Column(Integer, primary_key=True)
@declared_attr
def company_id(cls):
return Column(ForeignKey('company.id'))
@declared_attr
def company(cls):
return relationship("Company")
class Manager(Employee):
__tablename__ = 'manager'
name = Column(String(50))
manager_data = Column(String(40))
__mapper_args__ = {
'polymorphic_identity':'manager',
'concrete':True
}
Base.registry.configure()
然而,当我们使用我们的映射时,Manager
和Employee
都将拥有一个可独立使用的.company
属性:
session.execute(
select(Employee).filter(Employee.company.has(id=5))
)
参数:
strict_attrs –
当在基类上指定时,“严格”属性模式被启用,试图将基类上的 ORM 映射属性限制为仅当下立即存在的属性,同时仍保留“多态”加载行为。
2.0 版中新增。
另请参阅
ConcreteBase
具体表继承
抽象具体类
类签名
类sqlalchemy.ext.declarative.AbstractConcreteBase
(sqlalchemy.ext.declarative.extensions.ConcreteBase
)
class sqlalchemy.ext.declarative.ConcreteBase
用于‘具体’声明映射的辅助类。
ConcreteBase
会自动使用 polymorphic_union()
函数,针对所有映射为该类的子类的表。该函数通过 __declare_last__()
函数调用,这实质上是 after_configured()
事件的钩子。
ConcreteBase
为类本身生成一个映射表。与 AbstractConcreteBase
相比,后者不会。
示例:
代码语言:javascript复制from sqlalchemy.ext.declarative import ConcreteBase
class Employee(ConcreteBase, Base):
__tablename__ = 'employee'
employee_id = Column(Integer, primary_key=True)
name = Column(String(50))
__mapper_args__ = {
'polymorphic_identity':'employee',
'concrete':True}
class Manager(Employee):
__tablename__ = 'manager'
employee_id = Column(Integer, primary_key=True)
name = Column(String(50))
manager_data = Column(String(40))
__mapper_args__ = {
'polymorphic_identity':'manager',
'concrete':True}
polymorphic_union()
使用的鉴别器列的默认名称为 type
。为了适应映射的用例,其中映射表中的实际列已命名为 type
,可以通过设置 _concrete_discriminator_name
属性来配置鉴别器名称:
class Employee(ConcreteBase, Base):
_concrete_discriminator_name = '_concrete_discriminator'
自版本 1.3.19 中新增:为 ConcreteBase
添加了 _concrete_discriminator_name
属性,以便自定义虚拟鉴别器列名称。
自版本 1.4.2 中更改:只需将 _concrete_discriminator_name
属性放置在最基类上即可使所有子类正确生效。如果映射列名称与鉴别器名称冲突,则现在会显示显式错误消息,而在 1.3.x 系列中会有一些警告,然后生成一个无用的查询。
另请参阅
AbstractConcreteBase
具体表继承
代码语言:javascript复制class sqlalchemy.ext.declarative.DeferredReflection
一个用于基于延迟反射步骤构建映射的辅助类。
通常情况下,通过将一个 Table
对象设置为具有 autoload_with=engine 的 __table__
属性,可以使用反射来使用声明。一个声明性类。需要注意的是,在构建普通声明性映射的时候,Table
必须是完全反映的,或者至少有一个主键列,这意味着在类声明时必须可用 Engine
。
DeferredReflection
mixin 将映射器的构建移动到稍后的时间点,在调用首先反射到目前为止创建的所有 Table
对象的特定方法之后。类可以定义如下:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import DeferredReflection
Base = declarative_base()
class MyClass(DeferredReflection, Base):
__tablename__ = 'mytable'
在上面,MyClass
还没有映射。在上述方式定义了一系列类之后,可以使用 prepare()
反射所有表并创建映射:
engine = create_engine("someengine://...")
DeferredReflection.prepare(engine)
DeferredReflection
mixin 可以应用于单个类,用作声明基类本身,或用于自定义抽象类。使用抽象基类允许仅为特定准备步骤准备一部分类,这对于使用多个引擎的应用程序是必要的。例如,如果一个应用程序有两个引擎,您可能会使用两个基类,并分别准备每个基类,例如:
class ReflectedOne(DeferredReflection, Base):
__abstract__ = True
class ReflectedTwo(DeferredReflection, Base):
__abstract__ = True
class MyClass(ReflectedOne):
__tablename__ = 'mytable'
class MyOtherClass(ReflectedOne):
__tablename__ = 'myothertable'
class YetAnotherClass(ReflectedTwo):
__tablename__ = 'yetanothertable'
# ... etc.
在上面,ReflectedOne
和 ReflectedTwo
的类层次结构可以分别配置:
ReflectedOne.prepare(engine_one)
ReflectedTwo.prepare(engine_two)
成员
prepare()
另请参阅
使用 DeferredReflection - 在 使用声明式配置表 部分。
代码语言:javascript复制classmethod prepare(bind: Engine | Connection, **reflect_kw: Any) → None
反射所有当前 DeferredReflection
子类的所有 Table
对象
参数:
-
bind
–Engine
或Connection
实例 …versionchanged:: 2.0.16 现在也接受Connection
。 -
**reflect_kw
– 传递给MetaData.reflect()
的其他关键字参数,例如MetaData.reflect.views
。 新版本 2.0.16 中的内容。
Mypy / Pep-484 对 ORM 映射的支持
原文:
docs.sqlalchemy.org/en/20/orm/extensions/mypy.html
当使用直接引用 Column
对象而不是 SQLAlchemy 2.0 中引入的 mapped_column()
构造时,支持 PEP 484 类型注释以及 MyPy 类型检查工具。
自 2.0 版开始已被弃用:SQLAlchemy Mypy 插件已弃用,并且可能在 SQLAlchemy 2.1 发布时被移除。我们建议用户尽快迁移。
无法跨不断变化的 mypy 发布维护此插件,未来的稳定性不能保证。
现代 SQLAlchemy 现在提供了 完全符合 pep-484 的映射语法;请参阅链接的部分以获取迁移详情。
安装
仅适用于 SQLAlchemy 2.0:不应安装存根,并且应完全卸载诸如 sqlalchemy-stubs 和 sqlalchemy2-stubs 等软件包。
Mypy 包本身是一个依赖项。
可以使用 pip 使用“mypy”额外钩子安装 Mypy:
代码语言:javascript复制pip install sqlalchemy[mypy]
插件本身如 Configuring mypy to use Plugins 中描述的那样配置,使用 sqlalchemy.ext.mypy.plugin
模块名,例如在 setup.cfg
中:
[mypy]
plugins = sqlalchemy.ext.mypy.plugin
插件功能
Mypy 插件的主要目的是拦截并修改 SQLAlchemy 声明性映射 的静态定义,使其与它们在被其 Mapper
对象 instrumented 后的结构相匹配。这允许类结构本身以及使用类的代码对 Mypy 工具有意义,否则基于当前声明性映射的功能,这是不可能的。该插件类似于需要为类似 dataclasses 这样的库修改类的动态插件。
为了涵盖这种情况经常发生的主要区域,考虑以下 ORM 映射,使用 User
类的典型示例:
from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import declarative_base
# "Base" is a class that is created dynamically from the
# declarative_base() function
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
# "some_user" is an instance of the User class, which
# accepts "id" and "name" kwargs based on the mapping
some_user = User(id=5, name="user")
# it has an attribute called .name that's a string
print(f"Username: {some_user.name}")
# a select() construct makes use of SQL expressions derived from the
# User class itself
select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
上述,Mypy 扩展可以执行的步骤包括:
- 解释由
declarative_base()
生成的Base
动态类,以便从中继承的类被认为是映射的。它还可以适应在使用装饰器进行声明式映射(无声明式基类)中描述的类装饰器方法。 - 对在声明式“内联”样式中定义的 ORM 映射属性进行类型推断,例如上面示例中
User
类的id
和name
属性。这包括User
的实例将使用int
类型的id
和str
类型的name
。还包括当访问User.id
和User.name
类级属性时,如上面的select()
语句中所示,它们与 SQL 表达式行为兼容,这是从InstrumentedAttribute
属性描述符类派生的。 - 将
__init__()
方法应用于尚未包含显式构造函数的映射类,该构造函数接受检测到的所有映射属性的特定类型的关键字参数。
当 Mypy 插件处理上述文件时,传递给 Mypy 工具的结果静态类定义和 Python 代码等效于以下内容:
代码语言:javascript复制from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import Mapped
from sqlalchemy.orm.decl_api import DeclarativeMeta
class Base(metaclass=DeclarativeMeta):
__abstract__ = True
class User(Base):
__tablename__ = "user"
id: Mapped[Optional[int]] = Mapped._special_method(
Column(Integer, primary_key=True)
)
name: Mapped[Optional[str]] = Mapped._special_method(Column(String))
def __init__(self, id: Optional[int] = ..., name: Optional[str] = ...) -> None: ...
some_user = User(id=5, name="user")
print(f"Username: {some_user.name}")
select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
上述已经采取的关键步骤包括:
-
Base
类现在明确地是基于DeclarativeMeta
类定义的,而不再是一个动态类。 -
id
和name
属性是基于Mapped
类定义的,该类代表一个在类和实例级别表现出不同行为的 Python 描述符。Mapped
类现在是用于所有 ORM 映射属性的InstrumentedAttribute
类的基类。Mapped
被定义为一个针对任意 Python 类型的通用类,这意味着特定的Mapped
实例与特定的 Python 类型相关联,例如上面的Mapped[Optional[int]]
和Mapped[Optional[str]
。 - 声明性映射属性赋值的右侧被移除,因为这类似于
Mapper
类通常要执行的操作,即它将用InstrumentedAttribute
](…/internals.html#sqlalchemy.orm.InstrumentedAttribute “sqlalchemy.orm.InstrumentedAttribute”)的特定实例替换这些属性。原始表达式移动到一个函数调用中,这样可以仍然进行类型检查而不与表达式的左侧冲突。对于 Mypy 来说,左侧的类型注释足以理解属性的行为。 - 添加了
User.__init__()
方法的类型存根,其中包括了正确的关键字和数据类型。
用法
以下各小节将讨论到目前为止已经考虑到的符合 PEP-484 的各种使用情况。
基于 TypeEngine 的列的内省
对于包含显式数据类型的映射列,当它们被映射为内联属性时,映射类型将被自动内省:
代码语言:javascript复制class MyClass(Base):
# ...
id = Column(Integer, primary_key=True)
name = Column("employee_name", String(50), nullable=False)
other_name = Column(String(50))
上述,id
,name
和other_name
的最终类级数据类型将被内省为Mapped[Optional[int]]
,Mapped[Optional[str]]
和Mapped[Optional[str]]
。这些类型默认始终被认为是Optional
,即使对于主键和非空列也是如此。原因是因为虽然数据库列id
和name
不能为 NULL,但 Python 属性id
和name
很可能是None
,而不需要显式的构造函数:
>>> m1 = MyClass()
>>> m1.id
None
上述列的类型可以被显式地声明,提供了更清晰的自我文档化以及能够控制哪些类型是可选的两个优点:
代码语言:javascript复制class MyClass(Base):
# ...
id: int = Column(Integer, primary_key=True)
name: str = Column("employee_name", String(50), nullable=False)
other_name: Optional[str] = Column(String(50))
Mypy 插件将接受上述int
,str
和Optional[str]
并将它们转换为包含在其周围的Mapped[]
类型。Mapped[]
构造也可以被显式使用:
from sqlalchemy.orm import Mapped
class MyClass(Base):
# ...
id: Mapped[int] = Column(Integer, primary_key=True)
name: Mapped[str] = Column("employee_name", String(50), nullable=False)
other_name: Mapped[Optional[str]] = Column(String(50))
当类型是非可选时,这意味着从MyClass
的实例中访问的属性将被认为是非None
的:
mc = MyClass(...)
# will pass mypy --strict
name: str = mc.name
对于可选属性,Mypy 认为类型必须包含 None,否则就是Optional
:
mc = MyClass(...)
# will pass mypy --strict
other_name: Optional[str] = mc.name
无论映射的属性是否被标记为Optional
,__init__()
方法的生成都仍然认为所有关键字都是可选的。这再次与 SQLAlchemy ORM 在创建构造函数时实际执行的操作相匹配,不应与诸如 Python dataclasses
之类的验证系统的行为混淆,后者将生成一个根据注释匹配的构造函数,包括可选和必需的属性。
没有明确类型的列
包含ForeignKey
修饰符的列在 SQLAlchemy 声明映射中不需要指定数据类型。对于这种类型的属性,Mypy 插件将通知用户需要发送明确的类型:
# .. other imports
from sqlalchemy.sql.schema import ForeignKey
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id = Column(ForeignKey("user.id"))
插件将按以下方式传递消息:
代码语言:javascript复制$ mypy test3.py --strict
test3.py:20: error: [SQLAlchemy Mypy plugin] Can't infer type from
ORM mapped expression assigned to attribute 'user_id'; please specify a
Python type or Mapped[<python type>] on the left hand side.
Found 1 error in 1 file (checked 1 source file)
要解决问题,请对Address.user_id
列应用明确的类型注释:
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
使用命令式表映射列
在命令式表样式中,Column
定义位于与映射属性本身分开的Table
构造内。Mypy 插件不考虑这个Table
,而是支持可以明确声明属性,并且必须使用Mapped
类将其标识为映射属性:
class MyClass(Base):
__table__ = Table(
"mytable",
Base.metadata,
Column(Integer, primary_key=True),
Column("employee_name", String(50), nullable=False),
Column(String(50)),
)
id: Mapped[int]
name: Mapped[str]
other_name: Mapped[Optional[str]]
上述Mapped
注释被视为映射列,并将包含在默认构造函数中,同时为MyClass
在类级别和实例级别提供正确的类型配置文件。
映射关系
该插件对使用类型推断来检测关系类型有限支持。对于所有无法检测类型的情况,它将发出信息丰富的错误消息,并且在所有情况下,可以明确提供适当的类型,要么使用Mapped
类,要么选择在内联声明中省略它。插件还需要确定关系是指向集合还是标量,并且为此依赖于relationship.uselist
和/或relationship.collection_class
参数的显式值。如果这些参数都不存在,则需要明确的类型,以及如果relationship()
的目标类型是字符串或可调用对象,而不是类:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user = relationship(User)
上述映射将产生以下错误:
代码语言:javascript复制test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
collection for ORM mapped expression assigned to attribute 'user'
if both 'uselist' and 'collection_class' arguments are absent from the
relationship(); please specify a type annotation on the left hand side.
Found 1 error in 1 file (checked 1 source file)
可以通过使用relationship(User, uselist=False)
或提供类型来解决错误,在这种情况下是标量User
对象:
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user: User = relationship(User)
对于集合,类似的模式也适用,即在没有uselist=True
或relationship.collection_class
的情况下,可以使用诸如List
之类的集合注释。还可以完全适当地使用类的字符串名称进行注释,如 pep-484 所支持,确保根据需要在TYPE_CHECKING 块中导入类:
from typing import TYPE_CHECKING, List
from .mymodel import Base
if TYPE_CHECKING:
# if the target of the relationship is in another module
# that cannot normally be imported at runtime
from .myaddressmodel import Address
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: List["Address"] = relationship("Address")
与列一样,Mapped
类也可以显式应用:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user: Mapped[User] = relationship(User, back_populates="addresses")
使用 @declared_attr 和声明性混合类
declared_attr
类允许在类级别函数中声明声明性映射的属性,并且在使用声明性混合类时特别有用。对于这些函数,函数的返回类型应使用Mapped[]
构造或指示函数返回的确切对象类型进行注释。此外,“mixin”类(即不以declarative_base()
类扩展,也不使用诸如registry.mapped()
之类的方法映射的类)应该被装饰上 declarative_mixin()
装饰器,这为 Mypy 插件提供了一个提示,指明特定的类意图充当声明性混合类:
from sqlalchemy.orm import declarative_mixin, declared_attr
@declarative_mixin
class HasUpdatedAt:
@declared_attr
def updated_at(cls) -> Column[DateTime]: # uses Column
return Column(DateTime)
@declarative_mixin
class HasCompany:
@declared_attr
def company_id(cls) -> Mapped[int]: # uses Mapped
return Column(ForeignKey("company.id"))
@declared_attr
def company(cls) -> Mapped["Company"]:
return relationship("Company")
class Employee(HasUpdatedAt, HasCompany, Base):
__tablename__ = "employee"
id = Column(Integer, primary_key=True)
name = Column(String)
注意方法HasCompany.company
的实际返回类型与注释之间的不匹配。Mypy 插件将所有@declared_attr
函数转换为简单的带注释的属性,以避免这种复杂性:
# what Mypy sees
class HasCompany:
company_id: Mapped[int]
company: Mapped["Company"]
与数据类或其他类型敏感的属性系统相结合
在 将 ORM 映射应用到现有数据类(遗留数据类用法) 中的 Python 数据类集成示例存在一个问题;Python 数据类期望明确的类型,它将用于构建类,并且每个赋值语句中给定的值都是重要的。也就是说,必须确切地声明以下类才能被数据类接受:
代码语言:javascript复制mapper_registry: registry = registry()
@mapper_registry.mapped
@dataclass
class User:
__table__ = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
id: int = field(init=False)
name: Optional[str] = None
fullname: Optional[str] = None
nickname: Optional[str] = None
addresses: List[Address] = field(default_factory=list)
__mapper_args__ = { # type: ignore
"properties": {"addresses": relationship("Address")}
}
我们不能将我们的Mapped[]
类型应用于属性id
、name
等,因为它们会被@dataclass
装饰器拒绝。此外,Mypy 还有另一个专门用于数据类的插件,这也可能妨碍我们的操作。
上述类实际上会顺利通过 Mypy 的类型检查;我们唯一缺少的是在User
上的属性可用于 SQL 表达式,例如:
stmt = select(User.name).where(User.id.in_([1, 2, 3]))
为了提供一个解决方法,Mypy 插件具有一个额外的功能,我们可以指定一个额外的属性 _mypy_mapped_attrs
,它是一个包含类级对象或它们的字符串名称的列表。这个属性可以在 TYPE_CHECKING
变量内部是条件性的:
@mapper_registry.mapped
@dataclass
class User:
__table__ = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
id: int = field(init=False)
name: Optional[str] = None
fullname: Optional[str]
nickname: Optional[str]
addresses: List[Address] = field(default_factory=list)
if TYPE_CHECKING:
_mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]
__mapper_args__ = { # type: ignore
"properties": {"addresses": relationship("Address")}
}
使用上述方法,列在 _mypy_mapped_attrs
中的属性将应用 Mapped
类型信息,以便在类绑定上下文中使用 User
类时,它将表现为一个 SQLAlchemy 映射类。
安装
对于 仅适用于 SQLAlchemy 2.0:不应安装存根,而应完全卸载像 sqlalchemy-stubs 和 sqlalchemy2-stubs 这样的包。
Mypy 包本身是一个依赖项。
可以使用 pip 使用 “mypy” extras 钩子安装 Mypy:
代码语言:javascript复制pip install sqlalchemy[mypy]
插件本身配置如 配置 mypy 使用插件 中所述,使用 sqlalchemy.ext.mypy.plugin
模块名称,例如在 setup.cfg
中:
[mypy]
plugins = sqlalchemy.ext.mypy.plugin
插件的功能
Mypy 插件的主要目的是拦截和修改 SQLAlchemy 声明式映射 的静态定义,以使其与它们在被 Mapper
对象 仪器化 后的结构相匹配。这使得类结构本身以及使用类的代码对 Mypy 工具有意义,否则根据当前声明式映射的功能,这将不是情况。该插件类似于为像 dataclasses 这样的库所需的类似插件,这些插件在运行时动态地修改类。
要涵盖此类情况经常发生的主要领域,请考虑以下 ORM 映射,使用 User
类的典型示例:
from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import declarative_base
# "Base" is a class that is created dynamically from the
# declarative_base() function
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
# "some_user" is an instance of the User class, which
# accepts "id" and "name" kwargs based on the mapping
some_user = User(id=5, name="user")
# it has an attribute called .name that's a string
print(f"Username: {some_user.name}")
# a select() construct makes use of SQL expressions derived from the
# User class itself
select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
上面,Mypy 扩展可以执行的步骤包括:
- 对由
declarative_base()
生成的Base
动态类进行解释,以便继承它的类被知道是已映射的。它还可以适应 使用装饰器进行声明式映射(无声明式基类) 中描述的类装饰器方法。 - 对于在声明式“内联”样式中定义的 ORM 映射属性的类型推断,例如上面示例中
User
类的id
和name
属性。这包括User
实例将使用int
类型的id
和str
类型的name
。它还包括当访问User.id
和User.name
类级属性时,正如它们在上面的select()
语句中那样,它们与 SQL 表达式行为兼容,这是从InstrumentedAttribute
属性描述符类派生的。 - 将
__init__()
方法应用于尚未包含显式构造函数的映射类,该构造函数接受特定类型的关键字参数,用于检测到的所有映射属性。
当 Mypy 插件处理上述文件时,结果的静态类定义和传递给 Mypy 工具的 Python 代码等效于以下内容:
代码语言:javascript复制from sqlalchemy import Column, Integer, String, select
from sqlalchemy.orm import Mapped
from sqlalchemy.orm.decl_api import DeclarativeMeta
class Base(metaclass=DeclarativeMeta):
__abstract__ = True
class User(Base):
__tablename__ = "user"
id: Mapped[Optional[int]] = Mapped._special_method(
Column(Integer, primary_key=True)
)
name: Mapped[Optional[str]] = Mapped._special_method(Column(String))
def __init__(self, id: Optional[int] = ..., name: Optional[str] = ...) -> None: ...
some_user = User(id=5, name="user")
print(f"Username: {some_user.name}")
select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
以上已经采取的关键步骤包括:
-
Base
类现在明确地以DeclarativeMeta
类的形式定义,而不是动态类。 -
id
和name
属性是以Mapped
类的术语定义的,该类表示在类与实例级别上表现出不同行为的 Python 描述符。Mapped
类现在是用于所有 ORM 映射属性的InstrumentedAttribute
类的基类。Mapped
被定义为针对任意 Python 类型的通用类,这意味着Mapped
的特定出现与特定的 Python 类型相关联,例如上面的Mapped[Optional[int]]
和Mapped[Optional[str]]
。 - 声明式映射属性分配的右侧 已移除,因为这类似于
Mapper
类通常会执行的操作,即它将这些属性替换为InstrumentedAttribute
的具体实例。原始表达式移到一个函数调用中,这将允许它仍然被类型检查而不与表达式的左侧发生冲突。对于 Mypy 来说,左侧的类型注释足以理解属性的行为。 - 为
User.__init__()
方法添加了类型存根,其中包括正确的关键字和数据类型。
使用方法
以下各小节将讨论迄今为止已考虑到的个别用例的 pep-484 符合性。
基于 TypeEngine 的列的自省
对于包含显式数据类型的映射列,当它们作为内联属性映射时,映射类型将被自动解析:
代码语言:javascript复制class MyClass(Base):
# ...
id = Column(Integer, primary_key=True)
name = Column("employee_name", String(50), nullable=False)
other_name = Column(String(50))
在上面,id
、name
和 other_name
这些最终的类级别数据类型将被解析为 Mapped[Optional[int]]
、Mapped[Optional[str]]
和 Mapped[Optional[str]]
。这些类型默认情况下总是被认为是 Optional
的,即使对于主键和非空列也是如此。原因是因为虽然数据库列id
和name
不能为 NULL,但 Python 属性 id
和 name
在没有显式构造函数的情况下肯定可以是 None
:
>>> m1 = MyClass()
>>> m1.id
None
上述列的类型可以被显式地声明,提供了更清晰的自我文档说明以及能够控制哪些类型是可选的两个优点:
代码语言:javascript复制class MyClass(Base):
# ...
id: int = Column(Integer, primary_key=True)
name: str = Column("employee_name", String(50), nullable=False)
other_name: Optional[str] = Column(String(50))
Mypy 插件将接受上述的 int
、str
和 Optional[str]
,并将它们转换为包含在 Mapped[]
类型周围的类型。Mapped[]
结构也可以被显式使用:
from sqlalchemy.orm import Mapped
class MyClass(Base):
# ...
id: Mapped[int] = Column(Integer, primary_key=True)
name: Mapped[str] = Column("employee_name", String(50), nullable=False)
other_name: Mapped[Optional[str]] = Column(String(50))
当类型是非可选时,这意味着从 MyClass
实例访问的属性将被视为非 None:
mc = MyClass(...)
# will pass mypy --strict
name: str = mc.name
对于可选属性,Mypy 认为类型必须包括 None,否则为 Optional
:
mc = MyClass(...)
# will pass mypy --strict
other_name: Optional[str] = mc.name
无论映射属性是否被标记为 Optional
,生成的 __init__()
方法仍然将所有关键字视为可选的。这再次与 SQLAlchemy ORM 实际创建构造函数时的行为相匹配,不应与诸如 Python dataclasses
之类的验证系统的行为混淆,后者将生成一个与注释匹配的构造函数,以确定可选 vs. 必需属性的注解。
没有明确类型的列
包含 ForeignKey
修改器的列在 SQLAlchemy 声明式映射中不需要指定数据类型。对于这种类型的属性,Mypy 插件将通知用户需要发送一个显式类型:
# .. other imports
from sqlalchemy.sql.schema import ForeignKey
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id = Column(ForeignKey("user.id"))
插件将如下传递消息:
代码语言:javascript复制$ mypy test3.py --strict
test3.py:20: error: [SQLAlchemy Mypy plugin] Can't infer type from
ORM mapped expression assigned to attribute 'user_id'; please specify a
Python type or Mapped[<python type>] on the left hand side.
Found 1 error in 1 file (checked 1 source file)
要解决此问题,请为 Address.user_id
列应用显式类型注释:
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
使用命令式表格映射列
在命令式表格风格中,Column
定义位于一个与映射属性本身分离的 Table
结构内。Mypy 插件不考虑这个 Table
,而是支持可以显式声明属性,必须使用 Mapped
类来标识它们为映射属性:
class MyClass(Base):
__table__ = Table(
"mytable",
Base.metadata,
Column(Integer, primary_key=True),
Column("employee_name", String(50), nullable=False),
Column(String(50)),
)
id: Mapped[int]
name: Mapped[str]
other_name: Mapped[Optional[str]]
上述Mapped
注释被视为映射列,并将包含在默认构造函数中,以及在类级别和实例级别为MyClass
提供正确的类型配置文件。
映射关系
该插件对使用类型推断来检测关系的类型有限支持。对于所有这些无法检测到类型的情况,它都将发出一个信息丰富的错误消息,在所有情况下,可以明确提供适当的类型,要么使用Mapped
类,要么选择在内联声明中省略它。该插件还需要确定关系是指向集合还是标量,并且为此依赖于relationship.uselist
和/或relationship.collection_class
参数的显式值。如果这些参数都不存在,则需要明确的类型,以及如果relationship()
的目标类型是字符串或可调用对象而不是类,则也需要明确的类型:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user = relationship(User)
上述映射将产生以下错误:
代码语言:javascript复制test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
collection for ORM mapped expression assigned to attribute 'user'
if both 'uselist' and 'collection_class' arguments are absent from the
relationship(); please specify a type annotation on the left hand side.
Found 1 error in 1 file (checked 1 source file)
错误可以通过使用relationship(User, uselist=False)
或者提供类型来解决,例如标量User
对象:
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user: User = relationship(User)
对于集合,类似的模式也适用,如果没有uselist=True
或者relationship.collection_class
,可以使用集合注释,如List
。在注释中使用类的字符串名称也是完全合适的,这是由 pep-484 支持的,确保在适当的时候在TYPE_CHECKING 块中导入该类:
from typing import TYPE_CHECKING, List
from .mymodel import Base
if TYPE_CHECKING:
# if the target of the relationship is in another module
# that cannot normally be imported at runtime
from .myaddressmodel import Address
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: List["Address"] = relationship("Address")
与列相似,Mapped
类也可以显式地应用:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user: Mapped[User] = relationship(User, back_populates="addresses")
使用 @declared_attr 和声明性混合
declared_attr
类允许在类级函数中声明 Declarative 映射属性,并且在使用声明性混入时特别有用。对于这些函数,函数的返回类型应该使用Mapped[]
构造进行注释,或者指示函数返回的对象的确切类型。此外,未被映射的“混入”类(即不从declarative_base()
类继承,也不使用诸如registry.mapped()
之类的方法进行映射)应该用declarative_mixin()
装饰器进行修饰,这为 Mypy 插件提供了一个提示,表明特定类意图作为声明性混入:
from sqlalchemy.orm import declarative_mixin, declared_attr
@declarative_mixin
class HasUpdatedAt:
@declared_attr
def updated_at(cls) -> Column[DateTime]: # uses Column
return Column(DateTime)
@declarative_mixin
class HasCompany:
@declared_attr
def company_id(cls) -> Mapped[int]: # uses Mapped
return Column(ForeignKey("company.id"))
@declared_attr
def company(cls) -> Mapped["Company"]:
return relationship("Company")
class Employee(HasUpdatedAt, HasCompany, Base):
__tablename__ = "employee"
id = Column(Integer, primary_key=True)
name = Column(String)
注意像HasCompany.company
这样的方法的实际返回类型与注释的不匹配。Mypy 插件将所有@declared_attr
函数转换为简单的注释属性,以避免这种复杂性:
# what Mypy sees
class HasCompany:
company_id: Mapped[int]
company: Mapped["Company"]
与 Dataclasses 或其他类型敏感的属性系统结合
Python dataclasses 集成的示例在将 ORM 映射应用于现有数据类(传统数据类用法)中提出了一个问题;Python dataclasses 期望一个明确的类型,它将用于构建类,并且每个赋值语句中给定的值是重要的。也就是说,一个如下所示的类必须要准确地声明才能被 dataclasses 接受:
代码语言:javascript复制mapper_registry: registry = registry()
@mapper_registry.mapped
@dataclass
class User:
__table__ = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
id: int = field(init=False)
name: Optional[str] = None
fullname: Optional[str] = None
nickname: Optional[str] = None
addresses: List[Address] = field(default_factory=list)
__mapper_args__ = { # type: ignore
"properties": {"addresses": relationship("Address")}
}
我们无法将我们的Mapped[]
类型应用于属性id
、name
等,因为它们将被@dataclass
装饰器拒绝。此外,Mypy 还有另一个专门用于 dataclasses 的插件,这也可能影响我们的操作。
上述类实际上会通过 Mypy 的类型检查而没有问题;我们唯一缺少的是User
上的属性能够在 SQL 表达式中使用,比如:
stmt = select(User.name).where(User.id.in_([1, 2, 3]))
为了解决这个问题,Mypy 插件有一个额外的功能,我们可以指定一个额外的属性_mypy_mapped_attrs
,这是一个包含类级对象或它们的字符串名称的列表。这个属性可以在TYPE_CHECKING
变量内部进行条件判断:
@mapper_registry.mapped
@dataclass
class User:
__table__ = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
id: int = field(init=False)
name: Optional[str] = None
fullname: Optional[str]
nickname: Optional[str]
addresses: List[Address] = field(default_factory=list)
if TYPE_CHECKING:
_mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]
__mapper_args__ = { # type: ignore
"properties": {"addresses": relationship("Address")}
}
使用上述方法,列在_mypy_mapped_attrs
中列出的属性将应用于Mapped
类型信息,以便在类绑定上下文中使用User
类时,它将表现为一个 SQLAlchemy 映射类。
基于 TypeEngine 的列的内省
对于包含显式数据类型的映射列,当它们被映射为内联属性时,映射类型将自动进行内省:
代码语言:javascript复制class MyClass(Base):
# ...
id = Column(Integer, primary_key=True)
name = Column("employee_name", String(50), nullable=False)
other_name = Column(String(50))
在上面,id
、name
和other_name
的最终类级数据类型将被内省为Mapped[Optional[int]]
、Mapped[Optional[str]]
和Mapped[Optional[str]]
。类型默认始终被视为可选,即使对于主键和非空列也是如此。原因是因为虽然数据库列id
和name
不能为 NULL,但 Python 属性id
和name
可以毫无疑问地是None
,而不需要显式构造函数:
>>> m1 = MyClass()
>>> m1.id
None
上述列的类型可以明确声明,提供两个优势,即更清晰的自我文档化以及能够控制哪些类型是可选的:
代码语言:javascript复制class MyClass(Base):
# ...
id: int = Column(Integer, primary_key=True)
name: str = Column("employee_name", String(50), nullable=False)
other_name: Optional[str] = Column(String(50))
Mypy 插件将接受上述int
、str
和Optional[str]
,并将它们转换为包围它们的Mapped[]
类型。Mapped[]
结构也可以明确使用:
from sqlalchemy.orm import Mapped
class MyClass(Base):
# ...
id: Mapped[int] = Column(Integer, primary_key=True)
name: Mapped[str] = Column("employee_name", String(50), nullable=False)
other_name: Mapped[Optional[str]] = Column(String(50))
当类型为非可选时,这意味着从MyClass
实例中访问的属性将被视为非None
:
mc = MyClass(...)
# will pass mypy --strict
name: str = mc.name
对于可选属性,Mypy 认为类型必须包含 None,否则为Optional
:
mc = MyClass(...)
# will pass mypy --strict
other_name: Optional[str] = mc.name
无论映射属性是否被标记为Optional
,__init__()
方法的生成仍然考虑所有关键字都是可选的。这再次与 SQLAlchemy ORM 实际创建构造函数时的行为相匹配,不应与验证系统(如 Python dataclasses
)的行为混淆,后者将根据注释生成与可选与必需属性相匹配的构造函数。
不具有显式类型的列
包含 ForeignKey
修改器的列在 SQLAlchemy 声明性映射中不需要指定数据类型。对于这种类型的属性,Mypy 插件将通知用户需要发送显式类型:
# .. other imports
from sqlalchemy.sql.schema import ForeignKey
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id = Column(ForeignKey("user.id"))
插件将以以下方式发送消息:
代码语言:javascript复制$ mypy test3.py --strict
test3.py:20: error: [SQLAlchemy Mypy plugin] Can't infer type from
ORM mapped expression assigned to attribute 'user_id'; please specify a
Python type or Mapped[<python type>] on the left hand side.
Found 1 error in 1 file (checked 1 source file)
要解决此问题,请对Address.user_id
列应用显式类型注释:
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
使用命令式表映射列
在 命令式表风格 中,Column
定义放在一个独立于映射属性本身的 Table
结构中。Mypy 插件不考虑这个Table
,而是支持可以明确声明属性,并且必须使用 Mapped
类来标识它们为映射属性:
class MyClass(Base):
__table__ = Table(
"mytable",
Base.metadata,
Column(Integer, primary_key=True),
Column("employee_name", String(50), nullable=False),
Column(String(50)),
)
id: Mapped[int]
name: Mapped[str]
other_name: Mapped[Optional[str]]
上述Mapped
注释被视为映射列,并将包含在默认构造函数中,同时为MyClass
提供正确的类型配置文件,无论是在类级别还是实例级别。
映射关系
该插件对使用类型推断来检测关系类型有限支持。对于所有无法检测类型的情况,它将发出信息丰富的错误消息,并且在所有情况下,可以明确提供适当的类型,可以使用Mapped
类或选择性地省略内联声明。插件还需要确定关系是引用集合还是标量,为此依赖于relationship.uselist
和/或relationship.collection_class
参数的显式值。如果这些参数都不存在,则需要明确类型,以及如果relationship()
的目标类型是字符串或可调用的,而不是类:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user = relationship(User)
上述映射将产生以下错误:
代码语言:javascript复制test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
collection for ORM mapped expression assigned to attribute 'user'
if both 'uselist' and 'collection_class' arguments are absent from the
relationship(); please specify a type annotation on the left hand side.
Found 1 error in 1 file (checked 1 source file)
可以通过使用relationship(User, uselist=False)
或提供类型来解决错误,在这种情况下是标量User
对象:
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user: User = relationship(User)
对于集合,类似的模式适用,如果没有uselist=True
或relationship.collection_class
,可以使用List
等集合注释。在注释中使用类的字符串名称也是完全适当的,支持 pep-484,确保类在TYPE_CHECKING block中适当导入:
from typing import TYPE_CHECKING, List
from .mymodel import Base
if TYPE_CHECKING:
# if the target of the relationship is in another module
# that cannot normally be imported at runtime
from .myaddressmodel import Address
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: List["Address"] = relationship("Address")
与列一样,Mapped
类也可以显式应用:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id: int = Column(ForeignKey("user.id"))
user: Mapped[User] = relationship(User, back_populates="addresses")
使用@declared_attr 和声明性混合
declared_attr
类允许在类级函数中声明声明性映射属性,并且在使用声明性混合时特别有用。对于这些函数,函数的返回类型应该使用Mapped[]
构造进行注释,或者指示函数返回的确切对象类型。另外,未以其他方式映射的“混合”类(即不从declarative_base()
类扩展,也不使用诸如registry.mapped()
之类的方法进行映射)应该用declarative_mixin()
装饰器进行装饰,这为 Mypy 插件提供了一个提示,表明特定的类打算作为声明性混合使用:
from sqlalchemy.orm import declarative_mixin, declared_attr
@declarative_mixin
class HasUpdatedAt:
@declared_attr
def updated_at(cls) -> Column[DateTime]: # uses Column
return Column(DateTime)
@declarative_mixin
class HasCompany:
@declared_attr
def company_id(cls) -> Mapped[int]: # uses Mapped
return Column(ForeignKey("company.id"))
@declared_attr
def company(cls) -> Mapped["Company"]:
return relationship("Company")
class Employee(HasUpdatedAt, HasCompany, Base):
__tablename__ = "employee"
id = Column(Integer, primary_key=True)
name = Column(String)
请注意,像HasCompany.company
这样的方法的实际返回类型与其注释之间存在不匹配。Mypy 插件将所有@declared_attr
函数转换为简单的注释属性,以避免这种复杂性:
# what Mypy sees
class HasCompany:
company_id: Mapped[int]
company: Mapped["Company"]
与数据类或其他类型敏感的属性系统结合
Python 数据类集成示例中的将 ORM 映射应用到现有数据类(旧数据类使用)存在一个问题;Python 数据类期望一个明确的类型,它将用于构建类,并且在每个赋值语句中给定的值是重要的。也就是说,必须准确地声明如下的类才能被数据类接受:
代码语言:javascript复制mapper_registry: registry = registry()
@mapper_registry.mapped
@dataclass
class User:
__table__ = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
id: int = field(init=False)
name: Optional[str] = None
fullname: Optional[str] = None
nickname: Optional[str] = None
addresses: List[Address] = field(default_factory=list)
__mapper_args__ = { # type: ignore
"properties": {"addresses": relationship("Address")}
}
我们无法将我们的Mapped[]
类型应用于属性id
、name
等,因为它们将被@dataclass
装饰器拒绝。另外,Mypy 还有另一个专门针对数据类的插件,这也可能妨碍我们的操作。
上述类实际上将无障碍地通过 Mypy 的类型检查;我们唯一缺少的是User
上属性被用于 SQL 表达式的能力,例如:
stmt = select(User.name).where(User.id.in_([1, 2, 3]))
为此提供一种解决方案,Mypy 插件具有一个额外的功能,我们可以指定一个额外的属性_mypy_mapped_attrs
,它是一个包含类级对象或它们的字符串名称的列表。该属性可以在TYPE_CHECKING
变量中条件化:
@mapper_registry.mapped
@dataclass
class User:
__table__ = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
id: int = field(init=False)
name: Optional[str] = None
fullname: Optional[str]
nickname: Optional[str]
addresses: List[Address] = field(default_factory=list)
if TYPE_CHECKING:
_mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]
__mapper_args__ = { # type: ignore
"properties": {"addresses": relationship("Address")}
}
使用上述方法,将在_mypy_mapped_attrs
中列出的属性应用Mapped
类型信息,以便在类绑定上下文中使用User
类时,它将表现为 SQLAlchemy 映射类。
突变跟踪
原文:
docs.sqlalchemy.org/en/20/orm/extensions/mutable.html
提供对标量值的就地更改的跟踪支持,这些更改传播到拥有父对象上的 ORM 更改事件中。
在标量列值上建立可变性
“可变”结构的典型示例是 Python 字典。按照 SQL 数据类型对象 中介绍的示例,我们从一个自定义类型开始,该类型将 Python 字典编组为 JSON 字符串,然后再进行持久化:
代码语言:javascript复制from sqlalchemy.types import TypeDecorator, VARCHAR
import json
class JSONEncodedDict(TypeDecorator):
"Represents an immutable structure as a json-encoded string."
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
仅出于示例目的使用 json
。sqlalchemy.ext.mutable
扩展可与任何目标 Python 类型可能是可变的类型一起使用,包括 PickleType
、ARRAY
等。
使用 sqlalchemy.ext.mutable
扩展时,值本身会跟踪所有引用它的父对象。下面,我们展示了 MutableDict
字典对象的简单版本,它将 Mutable
mixin 应用于普通 Python 字典:
from sqlalchemy.ext.mutable import Mutable
class MutableDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
"Convert plain dictionaries to MutableDict."
if not isinstance(value, MutableDict):
if isinstance(value, dict):
return MutableDict(value)
# this call will raise ValueError
return Mutable.coerce(key, value)
else:
return value
def __setitem__(self, key, value):
"Detect dictionary set events and emit change events."
dict.__setitem__(self, key, value)
self.changed()
def __delitem__(self, key):
"Detect dictionary del events and emit change events."
dict.__delitem__(self, key)
self.changed()
上述字典类采用了子类化 Python 内置的 dict
的方法,以生成一个 dict 子类,该子类通过 __setitem__
将所有突变事件路由到。这种方法有其变体,例如子类化 UserDict.UserDict
或 collections.MutableMapping
;对于此示例而言,重要的部分是当数据结构发生就地更改时,将调用 Mutable.changed()
方法。
我们还重新定义了 Mutable.coerce()
方法,该方法将用于将不是 MutableDict
实例的任何值转换为适当的类型,例如 json
模块返回的普通字典。定义此方法是可选的;我们也可以创建我们的 JSONEncodedDict
,使其始终返回 MutableDict
的实例,并且还确保所有调用代码都显式使用 MutableDict
。当未覆盖 Mutable.coerce()
时,应用于父对象的任何不是可变类型实例的值都将引发 ValueError
。
我们的新 MutableDict
类型提供了一个类方法 Mutable.as_mutable()
,我们可以在列元数据中使用它来关联类型。该方法获取给定的类型对象或类,并关联一个监听器,该监听器将检测到该类型的所有未来映射,并对映射的属性应用事件监听仪器。例如,使用经典的表元数据:
from sqlalchemy import Table, Column, Integer
my_data = Table('my_data', metadata,
Column('id', Integer, primary_key=True),
Column('data', MutableDict.as_mutable(JSONEncodedDict))
)
在上面,Mutable.as_mutable()
返回一个 JSONEncodedDict
实例(如果类型对象尚不是实例),该实例将拦截针对该类型映射的任何属性。下面我们建立一个简单的映射与 my_data
表:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class MyDataClass(Base):
__tablename__ = 'my_data'
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))
MyDataClass.data
成员现在将收到对其值的原地更改的通知。
对 MyDataClass.data
成员的任何原地更改都会在父对象上标记属性为“脏”:
>>> from sqlalchemy.orm import Session
>>> sess = Session(some_engine)
>>> m1 = MyDataClass(data={'value1':'foo'})
>>> sess.add(m1)
>>> sess.commit()
>>> m1.data['value1'] = 'bar'
>>> assert m1 in sess.dirty
True
MutableDict
可以通过一步关联所有未来的 JSONEncodedDict
实例,使用 Mutable.associate_with()
。这类似于 Mutable.as_mutable()
,但它将无条件地拦截所有映射中所有 MutableDict
的出现,而无需单独声明它:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
MutableDict.associate_with(JSONEncodedDict)
class Base(DeclarativeBase):
pass
class MyDataClass(Base):
__tablename__ = 'my_data'
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[dict[str, str]] = mapped_column(JSONEncodedDict)
支持 Pickling
sqlalchemy.ext.mutable
扩展的关键在于在值对象上放置了一个 weakref.WeakKeyDictionary
,它存储了父映射对象到与该值相关联的属性名称的映射。 WeakKeyDictionary
对象不可 pickle,因为它们包含 weakrefs 和函数回调。在我们的情况下,这是件好事,因为如果这个字典是可 pickle 的,那么它可能会导致我们的值对象的 pickle 大小过大,因为它们在不涉及父对象上下文的情况下被单独 pickle。开发人员在这里的责任只是提供一个 __getstate__
方法,该方法将 MutableBase._parents()
集合从 pickle 流中排除:
class MyMutableType(Mutable):
def __getstate__(self):
d = self.__dict__.copy()
d.pop('_parents', None)
return d
对于我们的字典示例,我们需要返回字典本身的内容(并在 __setstate__
上也进行恢复):
class MutableDict(Mutable, dict):
# ....
def __getstate__(self):
return dict(self)
def __setstate__(self, state):
self.update(state)
如果我们的可变值对象作为它附加到的一个或多个父对象一起被 pickle,那么 Mutable
mixin 将在每个值对象上重新建立 Mutable._parents
集合,因为拥有父对象本身被 unpickle。
接收事件
AttributeEvents.modified()
事件处理程序可用于在可变标量发出更改事件时接收事件。 当从可变扩展内调用 flag_modified()
函数时,将调用此事件处理程序:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy import event
class Base(DeclarativeBase):
pass
class MyDataClass(Base):
__tablename__ = 'my_data'
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))
@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance, initiator):
print("json value modified:", instance.data)
```## 在复合上建立可变性
复合是一种特殊的 ORM 功能,允许将单个标量属性分配给一个对象值,该对象值表示从底层映射表的一个或多个列中“组合”而成的信息。 通常示例是几何“点”,并在 复合列类型 中介绍。
与 `Mutable` 一样,用户定义的复合类将 `MutableComposite` 作为一个混合类,通过 `MutableComposite.changed()` 方法检测并传递更改事件给其父对象。 在复合类的情况下,检测通常通过特殊的 Python 方法 `__setattr__()` 进行。 在下面的示例中,我们扩展了 复合列类型 中介绍的 `Point` 类,以包括 `MutableComposite` 在其基类中,并通过 `__setattr__` 将属性设置事件路由到 `MutableComposite.changed()` 方法:
```py
import dataclasses
from sqlalchemy.ext.mutable import MutableComposite
@dataclasses.dataclass
class Point(MutableComposite):
x: int
y: int
def __setattr__(self, key, value):
"Intercept set events"
# set the attribute
object.__setattr__(self, key, value)
# alert all parents to the change
self.changed()
MutableComposite
类利用类映射事件自动为任何使用指定我们的 Point
类型的 composite()
的地方建立监听器。 下面,当 Point
映射到 Vertex
类时,将建立监听器,这些监听器将将来自 Point
对象的更改事件路由到每个 Vertex.start
和 Vertex.end
属性:
from sqlalchemy.orm import DeclarativeBase, Mapped
from sqlalchemy.orm import composite, mapped_column
class Base(DeclarativeBase):
pass
class Vertex(Base):
__tablename__ = "vertices"
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[Point] = composite(mapped_column("x1"), mapped_column("y1"))
end: Mapped[Point] = composite(mapped_column("x2"), mapped_column("y2"))
def __repr__(self):
return f"Vertex(start={self.start}, end={self.end})"
对 Vertex.start
或 Vertex.end
成员的任何原地更改都将在父对象上标记该属性为“脏”:
>>> from sqlalchemy.orm import Session
>>> sess = Session(engine)
>>> v1 = Vertex(start=Point(3, 4), end=Point(12, 15))
>>> sess.add(v1)
sql>>> sess.flush()
BEGIN (implicit)
INSERT INTO vertices (x1, y1, x2, y2) VALUES (?, ?, ?, ?)
[...] (3, 4, 12, 15)
>>> v1.end.x = 8
>>> assert v1 in sess.dirty
True
sql>>> sess.commit()
UPDATE vertices SET x2=? WHERE vertices.id = ?
[...] (8, 1)
COMMIT
强制转换可变组合
MutableBase.coerce()
方法也支持复合类型。对于 MutableComposite
,MutableBase.coerce()
方法仅在属性设置操作时调用,而不在加载操作中调用。覆盖 MutableBase.coerce()
方法基本上等同于为使用自定义复合类型的所有属性使用 validates()
验证程序:
@dataclasses.dataclass
class Point(MutableComposite):
# other Point methods
# ...
def coerce(cls, key, value):
if isinstance(value, tuple):
value = Point(*value)
elif not isinstance(value, Point):
raise ValueError("tuple or Point expected")
return value
支持 Pickling
与 Mutable
类似,MutableComposite
辅助类使用 weakref.WeakKeyDictionary
,可通过 MutableBase._parents()
属性获得,该属性不可 picklable。如果我们需要 pickle Point
的实例或其所属的类 Vertex
,我们至少需要定义一个不包含 _parents
字典的 __getstate__
。下面我们定义了 Point
类的最小形式的 __getstate__
和 __setstate__
:
@dataclasses.dataclass
class Point(MutableComposite):
# ...
def __getstate__(self):
return self.x, self.y
def __setstate__(self, state):
self.x, self.y = state
与 Mutable
一样,MutableComposite
增强了父对象的对象关系状态的 pickling 过程,以便 MutableBase._parents()
集合被恢复到所有 Point
对象中。
API 参考
对象名称 | 描述 |
---|---|
Mutable | 混合类,定义对父对象的变更事件的透明传播。 |
MutableBase | Mutable 和 MutableComposite 的通用基类。 |
MutableComposite | 混合类,定义对 SQLAlchemy “composite” 对象的变更事件的透明传播,传播到其拥有的父对象或父对象。 |
MutableDict | 实现了 Mutable 的字典类型。 |
MutableList | 实现了 Mutable 的列表类型。 |
MutableSet | 实现Mutable的集合类型。 |
class sqlalchemy.ext.mutable.MutableBase
成员
_parents,coerce()
公共基类,用于Mutable
和MutableComposite
。
attribute _parents
父对象的InstanceState
->父对象上的属性名称的字典。
该属性是所谓的“记忆化”属性。首次访问时,它会使用一个新的weakref.WeakKeyDictionary
进行初始化,并在后续访问时返回相同的对象。
在 1.4 版本中更改:现在使用InstanceState
作为弱字典中的键,而不是实例本身。
classmethod coerce(key: str, value: Any) → Any | None
给定一个值,将其强制转换为目标类型。
可以被自定义子类重写,将传入数据强制转换为特定类型。
默认情况下,引发ValueError
。
根据父类是Mutable
类型还是MutableComposite
类型,在不同情况下调用此方法。对于前者,它在属性设置操作和 ORM 加载操作期间都会被调用。对于后者,它仅在属性设置操作期间被调用;composite()
构造的机制在加载操作期间处理强制转换。
参数:
-
key
– 正在设置的 ORM 映射属性的字符串名称。 -
value
– 输入值。
返回:
如果无法完成强制转换,则该方法应返回强制转换后的值,或引发ValueError
。
class sqlalchemy.ext.mutable.Mutable
定义透明传播更改事件到父对象的混入。
查看在标量列值上建立可变性中的示例以获取用法信息。
成员
_get_listen_keys(),_listen_on_attribute(),_parents,as_mutable(),associate_with(),associate_with_attribute(),changed(),coerce()
类签名
类sqlalchemy.ext.mutable.Mutable
(sqlalchemy.ext.mutable.MutableBase
)
classmethod _get_listen_keys(attribute: QueryableAttribute[Any]) → Set[str]
继承自 MutableBase
的 sqlalchemy.ext.mutable.MutableBase._get_listen_keys
方法
给定一个描述符属性,返回一个指示此属性状态变化的属性键的set()
。
这通常只是set([attribute.key])
,但可以被覆盖以提供额外的键。例如,MutableComposite
会用与组成复合值的列相关联的属性键来增加这个集合。
在拦截InstanceEvents.refresh()
和InstanceEvents.refresh_flush()
事件时,将查询此集合,这些事件传递了已刷新的属性名称列表;该列表与此集合进行比较,以确定是否需要采取行动。
classmethod _listen_on_attribute(attribute: QueryableAttribute[Any], coerce: bool, parent_cls: _ExternalEntityType[Any]) → None
继承自 MutableBase
的 sqlalchemy.ext.mutable.MutableBase._listen_on_attribute
方法
将此类型建立为给定映射描述符的变异监听器。
代码语言:javascript复制attribute _parents
继承自 MutableBase
的 sqlalchemy.ext.mutable.MutableBase._parents
属性
父对象的InstanceState
->父对象上的属性名的字典。
此属性是所谓的“记忆化”属性。它在第一次访问时使用一个新的weakref.WeakKeyDictionary
进行初始化,并在后续访问时返回相同的对象。
自版本 1.4 更改:InstanceState
现在作为弱字典中的键,而不是实例本身。
classmethod as_mutable(sqltype: TypeEngine) → TypeEngine
将 SQL 类型与此可变 Python 类型关联起来。
这将建立侦听器,以检测针对给定类型的 ORM 映射,并向这些映射添加变异事件跟踪器。
类型无条件地作为实例返回,因此可以内联使用as_mutable()
:
Table('mytable', metadata,
Column('id', Integer, primary_key=True),
Column('data', MyMutableType.as_mutable(PickleType))
)
请注意,返回的类型始终是一个实例,即使给定一个类,也只有明确声明了该类型实例的列才会接收到额外的仪器设备。
要将特定的可变类型与所有特定类型的所有出现相关联,请使用特定Mutable
子类的Mutable.associate_with()
类方法来建立全局关联。
警告
此方法建立的侦听器对所有映射器都是全局的,并且不会被垃圾回收。只能对应用程序中永久的类型使用as_mutable()
,不要与临时类型一起使用,否则这将导致内存使用量无限增长。
classmethod associate_with(sqltype: type) → None
将此包装器与未来的给定类型的映射列相关联。
这是一个方便的方法,会自动调用associate_with_attribute
。
警告
此方法建立的侦听器对所有映射器都是全局的,并且不会被垃圾回收。只能对应用程序中永久的类型使用associate_with()
,不要与临时类型一起使用,否则这将导致内存使用量无限增长。
classmethod associate_with_attribute(attribute: InstrumentedAttribute[_O]) → None
将此类型建立为给定映射描述符的变异侦听器。
代码语言:javascript复制method changed() → None
子类应该在发生变更事件时调用此方法。
代码语言:javascript复制classmethod coerce(key: str, value: Any) → Any | None
继承自 MutableBase.coerce()
方法的 MutableBase
给定一个值,将其强制转换为目标类型。
可以由自定义子类重写以将传入数据强制转换为特定类型。
默认情况下,引发ValueError
。
根据父类是Mutable
类型还是MutableComposite
类型,在不同的情况下调用此方法。对于前者,在属性设置操作和 ORM 加载操作期间都会调用它。对于后者,在属性设置操作期间才会调用它;composite()
构造的机制处理加载操作期间的强制转换。
参数:
-
key
– 正在设置的 ORM 映射属性的字符串名称。 -
value
– 输入值。
返回:
如果无法完成转换,则该方法应返回转换后的值,或引发ValueError
。
class sqlalchemy.ext.mutable.MutableComposite
混入,定义了将 SQLAlchemy“组合”对象上的变更事件透明传播到其拥有的父对象的机制。
查看在组合上建立可变性中的示例以获取用法信息。
成员
changed()
类签名
类 sqlalchemy.ext.mutable.MutableComposite
(sqlalchemy.ext.mutable.MutableBase
)
method changed() → None
子类应在更改事件发生时调用此方法。
代码语言:javascript复制class sqlalchemy.ext.mutable.MutableDict
一种实现了 Mutable
的字典类型。
MutableDict
对象实现了一个字典,当更改字典的内容时会向底层映射发送更改事件,包括添加或删除值时。
请注意,MutableDict
不会将可变跟踪应用于字典内部的值本身。因此,它不足以解决跟踪对递归字典结构进行深层更改的用例,例如 JSON 结构。要支持此用例,请构建 MutableDict
的子类,该子类提供适当的强制转换,以便将放置在字典中的值也“可变”,并将事件发送到其父结构。
另请参阅
MutableList
MutableSet
成员
clear(), coerce(), pop(), popitem(), setdefault(), update()
类签名
类 sqlalchemy.ext.mutable.MutableDict
(sqlalchemy.ext.mutable.Mutable
,builtins.dict
,typing.Generic
)
method clear() → None. Remove all items from D.
代码语言:javascript复制classmethod coerce(key: str, value: Any) → MutableDict[_KT, _VT] | None
将普通字典转换为此类的实例。
代码语言:javascript复制method pop(k[, d]) → v, remove specified key and return the corresponding value.
如果找不到键,则在给定默认值的情况下返回;否则,引发 KeyError。
代码语言:javascript复制method popitem() → Tuple[_KT, _VT]
移除并返回一个(键,值)对作为 2 元组。
以 LIFO(后进先出)顺序返回键值对。如果字典为空,则引发 KeyError。
代码语言:javascript复制method setdefault(*arg)
如果字典中没有键,则将键插入并将其值设置为默认值。
如果字典中存在键,则返回键的值,否则返回默认值。
代码语言:javascript复制method update([E, ]**F) → None. Update D from dict/iterable E and F.
如果 E 存在且具有 .keys()
方法,则执行以下操作:for k in E: D[k] = E[k] 如果 E 存在但缺少 .keys()
方法,则执行以下操作:for k, v in E: D[k] = v 在任一情况下,接下来执行以下操作:for k in F: D[k] = F[k]
class sqlalchemy.ext.mutable.MutableList
一种实现了 Mutable
的列表类型。
MutableList
对象实现了一个列表,在修改列表内容时会向底层映射发出更改事件,包括添加或删除值时。
注意MutableList
不会对列表内部的值本身应用可变跟踪。因此,它不能解决跟踪递归可变结构(例如 JSON 结构)的深层更改的用例。要支持此用例,构建MutableList
的子类,提供适当的强制转换以使放置在字典中的值也是“可变的”,并将事件传播到其父结构。
另请参见
MutableDict
MutableSet
成员
append(), clear(), coerce(), extend(), insert(), is_iterable(), is_scalar(), pop(), remove(), reverse(), sort()
类签名
类sqlalchemy.ext.mutable.MutableList
(sqlalchemy.ext.mutable.Mutable
,builtins.list
,typing.Generic
)
method append(x: _T) → None
将对象追加到列表末尾。
代码语言:javascript复制method clear() → None
从列表中删除所有项。
代码语言:javascript复制classmethod coerce(key: str, value: MutableList[_T] | _T) → MutableList[_T] | None
将普通列表转换为此类的实例。
代码语言:javascript复制method extend(x: Iterable[_T]) → None
通过从可迭代对象中追加元素来扩展列表。
代码语言:javascript复制method insert(i: SupportsIndex, x: _T) → None
在索引之前插入对象。
代码语言:javascript复制method is_iterable(value: _T | Iterable[_T]) → TypeGuard[Iterable[_T]]
代码语言:javascript复制method is_scalar(value: _T | Iterable[_T]) → TypeGuard[_T]
代码语言:javascript复制method pop(*arg: SupportsIndex) → _T
移除并返回索引处的项(默认为最后一个)。
如果列表为空或索引超出范围,则引发 IndexError。
代码语言:javascript复制method remove(i: _T) → None
移除第一次出现的值。
如果值不存在,则引发 ValueError。
代码语言:javascript复制method reverse() → None
原地反转。
代码语言:javascript复制method sort(**kw: Any) → None
对列表进行升序排序并返回 None。
排序是原地进行的(即修改列表本身)并且是稳定的(即保持两个相等元素的顺序)。
如果给定了键函数,则将其一次应用于每个列表项并根据其函数值升序或降序排序。
反转标志可以设置为按降序排序。
代码语言:javascript复制class sqlalchemy.ext.mutable.MutableSet
实现了Mutable
的集合类型。
MutableSet
对象实现了一个集合,当集合的内容发生更改时,将向底层映射发出更改事件,包括添加或删除值时。
请注意,MutableSet
不会对集合中值本身应用可变跟踪。因此,它不是跟踪递归可变结构的深层更改的足够解决方案。为了支持这种用例,请构建一个MutableSet
的子类,该子类提供适当的强制转换,使放置在字典中的值也是“可变的”,并向其父结构发出事件。
另请参阅
MutableDict
MutableList
成员
add(), clear(), coerce(), difference_update(), discard(), intersection_update(), pop(), remove(), symmetric_difference_update(), update()
类签名
类 sqlalchemy.ext.mutable.MutableSet
(sqlalchemy.ext.mutable.Mutable
, builtins.set
, typing.Generic
)
method add(elem: _T) → None
向集合添加一个元素。
如果元素已经存在,则不起作用。
代码语言:javascript复制method clear() → None
从此集合中移除所有元素。
代码语言:javascript复制classmethod coerce(index: str, value: Any) → MutableSet[_T] | None
将普通集合转换为此类的实例。
代码语言:javascript复制method difference_update(*arg: Iterable[Any]) → None
从此集合中删除另一个集合的所有元素。
代码语言:javascript复制method discard(elem: _T) → None
如果元素是成员,则从集合中删除一个元素。
如果元素不是成员,则不执行任何操作。
代码语言:javascript复制method intersection_update(*arg: Iterable[Any]) → None
使用自身与另一个集合的交集更新集合。
代码语言:javascript复制method pop(*arg: Any) → _T
移除并返回一个任意的集合元素。如果集合为空,则引发 KeyError。
代码语言:javascript复制method remove(elem: _T) → None
从集合中删除一个元素;它必须是成员。
如果元素不是成员,则引发 KeyError。
代码语言:javascript复制method symmetric_difference_update(*arg: Iterable[_T]) → None
使用自身与另一个集合的对称差更新集合。
代码语言:javascript复制method update(*arg: Iterable[_T]) → None
使用自身与其他集合的并集更新集合。
在标量列值上建立可变性
“可变”结构的典型示例是 Python 字典。在 SQL 数据类型对象中介绍的示例中,我们从自定义类型开始,该类型在持久化之前将 Python 字典编组为 JSON 字符串:
代码语言:javascript复制from sqlalchemy.types import TypeDecorator, VARCHAR
import json
class JSONEncodedDict(TypeDecorator):
"Represents an immutable structure as a json-encoded string."
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
使用json
仅用于示例目的。sqlalchemy.ext.mutable
扩展可以与任何目标 Python 类型可能是可变的类型一起使用,包括PickleType
、ARRAY
等。
当使用sqlalchemy.ext.mutable
扩展时,值本身跟踪所有引用它的父对象。下面,我们展示了一个简单版本的MutableDict
字典对象,它将Mutable
mixin 应用于普通的 Python 字典:
from sqlalchemy.ext.mutable import Mutable
class MutableDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
"Convert plain dictionaries to MutableDict."
if not isinstance(value, MutableDict):
if isinstance(value, dict):
return MutableDict(value)
# this call will raise ValueError
return Mutable.coerce(key, value)
else:
return value
def __setitem__(self, key, value):
"Detect dictionary set events and emit change events."
dict.__setitem__(self, key, value)
self.changed()
def __delitem__(self, key):
"Detect dictionary del events and emit change events."
dict.__delitem__(self, key)
self.changed()
上述字典类采用了子类化 Python 内置的dict
的方法,以产生一个 dict 子类,通过__setitem__
路由所有的变异事件。这种方法还有变体,比如子类化UserDict.UserDict
或collections.MutableMapping
;对于这个示例很重要的部分是,每当对数据结构进行就地更改时,都会调用Mutable.changed()
方法。
我们还重新定义了Mutable.coerce()
方法,用于将不是MutableDict
实例的任何值转换为适当的类型,比如json
模块返回的普通字典。定义这个方法是可选的;我们也可以创建我们的JSONEncodedDict
,使其始终返回MutableDict
的实例,并确保所有调用代码都明确使用MutableDict
。当未覆盖Mutable.coerce()
时,应用于父对象的任何不是可变类型实例的值将引发ValueError
。
我们的新MutableDict
类型提供了一个类方法Mutable.as_mutable()
,我们可以在列元数据中使用它与类型关联。这个方法获取给定的类型对象或类,并关联一个监听器,将检测到所有将来映射到该类型的映射,应用事件监听仪器到映射的属性。例如,使用经典表元数据:
from sqlalchemy import Table, Column, Integer
my_data = Table('my_data', metadata,
Column('id', Integer, primary_key=True),
Column('data', MutableDict.as_mutable(JSONEncodedDict))
)
上面,Mutable.as_mutable()
返回一个JSONEncodedDict
的实例(如果类型对象尚未是一个实例),它将拦截任何映射到该类型的属性。下面我们建立一个简单的映射到my_data
表:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class MyDataClass(Base):
__tablename__ = 'my_data'
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))
MyDataClass.data
成员现在将被通知其值的原地更改。
对 MyDataClass.data
成员的任何原地更改都将标记父对象的属性为“脏”:
>>> from sqlalchemy.orm import Session
>>> sess = Session(some_engine)
>>> m1 = MyDataClass(data={'value1':'foo'})
>>> sess.add(m1)
>>> sess.commit()
>>> m1.data['value1'] = 'bar'
>>> assert m1 in sess.dirty
True
MutableDict
可以通过一个步骤与所有未来的 JSONEncodedDict
实例关联,使用 Mutable.associate_with()
。这类似于 Mutable.as_mutable()
,但它将无条件拦截所有映射中 MutableDict
的所有出现,而无需单独声明它:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
MutableDict.associate_with(JSONEncodedDict)
class Base(DeclarativeBase):
pass
class MyDataClass(Base):
__tablename__ = 'my_data'
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[dict[str, str]] = mapped_column(JSONEncodedDict)
支持 Pickling
sqlalchemy.ext.mutable
扩展的关键在于在值对象上放置一个 weakref.WeakKeyDictionary
,它存储了父映射对象的映射,键为它们与该值相关联的属性名。 WeakKeyDictionary
对象不可 pickle,因为它们包含弱引用和函数回调。在我们的情况下,这是一件好事,因为如果这个字典是可 pickle 的,它可能会导致独立于父对象上下文的值对象的 pickle 大小过大。在这里,开发者的责任仅仅是提供一个 __getstate__
方法,从 pickle 流中排除 MutableBase._parents()
集合:
class MyMutableType(Mutable):
def __getstate__(self):
d = self.__dict__.copy()
d.pop('_parents', None)
return d
对于我们的字典示例,我们需要返回字典本身的内容(并在 setstate 中还原它们):
代码语言:javascript复制class MutableDict(Mutable, dict):
# ....
def __getstate__(self):
return dict(self)
def __setstate__(self, state):
self.update(state)
如果我们的可变值对象被 pickle,而它附加到一个或多个也是 pickle 的父对象上,Mutable
mixin 将在每个值对象上重新建立 Mutable._parents
集合,因为拥有父对象本身被 unpickle。
接收事件
当一个可变标量发出变更事件时,AttributeEvents.modified()
事件处理程序可以用于接收事件。当在可变扩展内部调用 flag_modified()
函数时,将调用此事件处理程序:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy import event
class Base(DeclarativeBase):
pass
class MyDataClass(Base):
__tablename__ = 'my_data'
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))
@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance, initiator):
print("json value modified:", instance.data)
支持 Pickling
sqlalchemy.ext.mutable
扩展的关键在于在值对象上放置一个 weakref.WeakKeyDictionary
,该字典存储父映射对象的映射,以属性名称为键,这些父映射对象与该值相关联。由于 WeakKeyDictionary
对象包含弱引用和函数回调,因此它们不可 picklable。在我们的情况下,这是一件好事,因为如果这个字典是可 pickle 的,那么它可能会导致我们的值对象的 pickle 大小过大,这些值对象是在不涉及父对象的情况下 pickle 的。开发者在这里的责任只是提供一个 __getstate__
方法,该方法从 pickle 流中排除了 MutableBase._parents()
集合:
class MyMutableType(Mutable):
def __getstate__(self):
d = self.__dict__.copy()
d.pop('_parents', None)
return d
对于我们的字典示例,我们需要返回字典本身的内容(并在 setstate 中还原它们):
代码语言:javascript复制class MutableDict(Mutable, dict):
# ....
def __getstate__(self):
return dict(self)
def __setstate__(self, state):
self.update(state)
在我们可变的值对象作为 pickle 对象时,如果它附加在一个或多个也是 pickle 的父对象上,Mutable
mixin 将在每个值对象上重新建立 Mutable._parents
集合,因为拥有父对象的本身会被 unpickle。
接收事件
AttributeEvents.modified()
事件处理程序可用于在可变标量发出更改事件时接收事件。当在可变扩展中调用 flag_modified()
函数时,将调用此事件处理程序:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy import event
class Base(DeclarativeBase):
pass
class MyDataClass(Base):
__tablename__ = 'my_data'
id: Mapped[int] = mapped_column(primary_key=True)
data: Mapped[dict[str, str]] = mapped_column(MutableDict.as_mutable(JSONEncodedDict))
@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance, initiator):
print("json value modified:", instance.data)
确立组合物的可变性
组合物是 ORM 的一种特殊功能,它允许将单个标量属性分配给一个对象值,该对象值表示从底层映射表中的一个或多个列中“组合”出的信息。通常的例子是几何“点”,并在 Composite Column Types 中介绍。
与Mutable
类似,用户定义的复合类作为一个混合类继承MutableComposite
,通过MutableComposite.changed()
方法检测并传递更改事件给其父类。对于复合类,通常是通过使用特殊的 Python 方法__setattr__()
来进行检测。在下面的示例中,我们扩展了复合列类型中介绍的Point
类,将MutableComposite
包含在其基类中,并通过__setattr__
将属性设置事件路由到MutableComposite.changed()
方法:
import dataclasses
from sqlalchemy.ext.mutable import MutableComposite
@dataclasses.dataclass
class Point(MutableComposite):
x: int
y: int
def __setattr__(self, key, value):
"Intercept set events"
# set the attribute
object.__setattr__(self, key, value)
# alert all parents to the change
self.changed()
MutableComposite
类利用类映射事件自动为任何指定我们的Point
类型的composite()
的使用建立监听器。下面,当Point
映射到Vertex
类时,将建立监听器,这些监听器将把Point
对象的更改事件路由到Vertex.start
和Vertex.end
属性中的每一个:
from sqlalchemy.orm import DeclarativeBase, Mapped
from sqlalchemy.orm import composite, mapped_column
class Base(DeclarativeBase):
pass
class Vertex(Base):
__tablename__ = "vertices"
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[Point] = composite(mapped_column("x1"), mapped_column("y1"))
end: Mapped[Point] = composite(mapped_column("x2"), mapped_column("y2"))
def __repr__(self):
return f"Vertex(start={self.start}, end={self.end})"
对Vertex.start
或Vertex.end
成员的任何就地更改都会在父对象上标记属性为“脏”:
>>> from sqlalchemy.orm import Session
>>> sess = Session(engine)
>>> v1 = Vertex(start=Point(3, 4), end=Point(12, 15))
>>> sess.add(v1)
sql>>> sess.flush()
BEGIN (implicit)
INSERT INTO vertices (x1, y1, x2, y2) VALUES (?, ?, ?, ?)
[...] (3, 4, 12, 15)
>>> v1.end.x = 8
>>> assert v1 in sess.dirty
True
sql>>> sess.commit()
UPDATE vertices SET x2=? WHERE vertices.id = ?
[...] (8, 1)
COMMIT
强制可变复合类型
在复合类型上也支持MutableBase.coerce()
方法。对于MutableComposite
,MutableBase.coerce()
方法仅在属性设置操作中调用,而不是在加载操作中调用。覆盖MutableBase.coerce()
方法基本上等同于为使用自定义复合类型的所有属性使用validates()
验证程序:
@dataclasses.dataclass
class Point(MutableComposite):
# other Point methods
# ...
def coerce(cls, key, value):
if isinstance(value, tuple):
value = Point(*value)
elif not isinstance(value, Point):
raise ValueError("tuple or Point expected")
return value
支持 Pickling
与Mutable
类似,MutableComposite
辅助类使用了通过MutableBase._parents()
属性获得的weakref.WeakKeyDictionary
,该字典不可 pickle。如果我们需要 pickle Point
或其拥有类 Vertex
的实例,至少需要定义一个不包含 _parents
字典的__getstate__
。下面我们定义了Point
类的最小形式的__getstate__
和__setstate__
:
@dataclasses.dataclass
class Point(MutableComposite):
# ...
def __getstate__(self):
return self.x, self.y
def __setstate__(self, state):
self.x, self.y = state
与Mutable
类似,MutableComposite
增强了父对象的对象关系状态的 pickling 过程,以便将MutableBase._parents()
集合还原为所有Point
对象。
强制转换可变复合类型
MutableBase.coerce()
方法也支持复合类型。在MutableComposite
的情况下,MutableBase.coerce()
方法仅在属性设置操作而非加载操作时调用。覆盖MutableBase.coerce()
方法基本上等同于对使用自定义复合类型的所有属性使用validates()
验证程序:
@dataclasses.dataclass
class Point(MutableComposite):
# other Point methods
# ...
def coerce(cls, key, value):
if isinstance(value, tuple):
value = Point(*value)
elif not isinstance(value, Point):
raise ValueError("tuple or Point expected")
return value
支持 Pickling
与Mutable
类似,MutableComposite
辅助类使用了通过MutableBase._parents()
属性获得的weakref.WeakKeyDictionary
,该字典不可 pickle。如果我们需要 pickle Point
或其拥有类 Vertex
的实例,至少需要定义一个不包含 _parents
字典的__getstate__
。下面我们定义了Point
类的最小形式的__getstate__
和__setstate__
:
@dataclasses.dataclass
class Point(MutableComposite):
# ...
def __getstate__(self):
return self.x, self.y
def __setstate__(self, state):
self.x, self.y = state
与Mutable
一样,MutableComposite
增强了父对象的对象关系状态的 pickling 过程,以便将MutableBase._parents()
集合还原为所有Point
对象。
API 参考
对象名称 | 描述 |
---|---|
Mutable | 定义更改事件透明传播到父对象的混合类。 |
MutableBase | Mutable和MutableComposite的通用基类。 |
MutableComposite | 定义 SQLAlchemy “composite” 对象上的更改事件透明传播到其拥有的父对象或父对象的混合类。 |
MutableDict | 一个实现Mutable的字典类型。 |
MutableList | 一个实现Mutable的列表类型。 |
MutableSet | 一个实现Mutable的集合类型。 |
class sqlalchemy.ext.mutable.MutableBase
成员
_parents, coerce()
Mutable
和MutableComposite
的通用基类。
attribute _parents
父对象的InstanceState
字典->父对象上的属性名称。
此属性是所谓的“记忆化”属性。第一次访问时,它会用一个新的weakref.WeakKeyDictionary
初始化自己,并在后续访问时返回相同的对象。
在 1.4 版中更改:InstanceState
现在被用作弱字典中的键,而不是实例本身。
classmethod coerce(key: str, value: Any) → Any | None
给定一个值,将其强制转换为目标类型。
可以被自定义子类覆盖,将传入数据强制转换为特定类型。
默认情况下,引发ValueError
。
根据父类是Mutable
类型还是MutableComposite
类型,在不同的情况下调用此方法。对于前者,它在属性集操作和 ORM 加载操作期间都会被调用。对于后者,它仅在属性集操作期间被调用;composite()
构造的机制在加载操作期间处理强制转换。
参数:
-
key
– 正在设置的 ORM 映射属性的字符串名称。 -
value
– 传入的值。
返回:
如果无法完成强制转换,该方法应返回强制转换后的值,或引发ValueError
。
class sqlalchemy.ext.mutable.Mutable
定义将更改事件透明传播到父对象的混合类。
查看在标量列值上建立可变性中的示例以获取用法信息。
成员
_get_listen_keys(), _listen_on_attribute(), _parents, as_mutable(), associate_with(), associate_with_attribute(), changed(), coerce()
类签名
类sqlalchemy.ext.mutable.Mutable
(sqlalchemy.ext.mutable.MutableBase
)
classmethod _get_listen_keys(attribute: QueryableAttribute[Any]) → Set[str]
继承自 MutableBase
的 sqlalchemy.ext.mutable.MutableBase._get_listen_keys
方法
给定一个描述符属性,返回指示此属性状态变化的属性键的set()
。
通常只是set([attribute.key])
,但可以被覆盖以提供额外的键。例如,MutableComposite
会用包含组合值的列相关联的属性键来增加这个集合。
在拦截InstanceEvents.refresh()
和InstanceEvents.refresh_flush()
事件时,会查询此集合,这些事件会传递一个已刷新的属性名称列表;该列表将与此集合进行比较,以确定是否需要采取行动。
classmethod _listen_on_attribute(attribute: QueryableAttribute[Any], coerce: bool, parent_cls: _ExternalEntityType[Any]) → None
继承自 MutableBase
的 sqlalchemy.ext.mutable.MutableBase._listen_on_attribute
方法
将此类型作为给定映射描述符的变异监听器。
代码语言:javascript复制attribute _parents
继承自 MutableBase
的 sqlalchemy.ext.mutable.MutableBase._parents
属性
父对象的InstanceState
->父对象上的属性名称的字典。
此属性是所谓的“记忆化”属性。它在首次访问时使用一个新的weakref.WeakKeyDictionary
进行初始化,并在后续访问时返回相同的对象。
在 1.4 版本中更改:现在使用InstanceState
作为弱字典中的键,而不是实例本身。
classmethod as_mutable(sqltype: TypeEngine) → TypeEngine
将 SQL 类型与此可变 Python 类型关联。
这将建立监听器,用于检测针对给定类型的 ORM 映射,向这些映射添加变异事件跟踪器。
该类型无条件地作为一个实例返回,以便可以内联使用as_mutable()
:
Table('mytable', metadata,
Column('id', Integer, primary_key=True),
Column('data', MyMutableType.as_mutable(PickleType))
)
请注意,返回的类型始终是一个实例,即使给定一个类,也只有明确声明了该类型实例的列才会接收到额外的仪器化。
要将特定的可变类型与特定类型的所有出现关联起来,请使用Mutable.associate_with()
类方法的特定Mutable
子类来建立全局关联。
警告
此方法建立的监听器是全局的,适用于所有映射器,并且不会被垃圾回收。只能对应用程序中永久的类型使用as_mutable()
,而不是临时类型,否则会导致内存使用量无限增长。
classmethod associate_with(sqltype: type) → None
将此包装器与将来的给定类型的映射列关联起来。
这是一个方便的方法,会自动调用associate_with_attribute
。
警告
该方法建立的监听器是全局的,适用于所有映射器,并且不会被垃圾回收。只能对应用程序中永久的类型使用associate_with()
,而不是临时类型,否则会导致内存使用量无限增长。
classmethod associate_with_attribute(attribute: InstrumentedAttribute[_O]) → None
将此类型作为给定映射描述符的变异监听器。
代码语言:javascript复制method changed() → None
子类在发生更改事件时应调用此方法。
代码语言:javascript复制classmethod coerce(key: str, value: Any) → Any | None
继承自 MutableBase.coerce()
方法 的 MutableBase
给定一个值,将其强制转换为目标类型。
可以被自定义子类重写以将传入的数据强制转换为特定类型。
默认情况下,引发 ValueError
。
此方法在不同的情况下被调用,具体取决于父类是 Mutable
类型还是 MutableComposite
类型。在前者的情况下,它将在属性设置操作以及 ORM 加载操作期间被调用。对于后者,它仅在属性设置操作期间被调用;composite()
构造的机制在加载操作期间处理强制转换。
参数:
-
key
– 被设置的 ORM 映射属性的字符串名称。 -
value
– 传入的值。
返回:
如果无法完成强制转换,则该方法应返回强制转换后的值,或引发 ValueError
。
class sqlalchemy.ext.mutable.MutableComposite
定义了对 SQLAlchemy “组合”对象的更改事件的透明传播的混合类到其拥有的父对象。
请参阅 在组合上建立可变性 中的示例以获取用法信息。
成员
changed()
类签名
class sqlalchemy.ext.mutable.MutableComposite
(sqlalchemy.ext.mutable.MutableBase
)
method changed() → None
子类应在更改事件发生时调用此方法。
代码语言:javascript复制class sqlalchemy.ext.mutable.MutableDict
实现了 Mutable
的字典类型。
MutableDict
对象实现了一个字典,在字典内容发生更改时将向基础映射发出更改事件,包括添加或移除值时。
请注意,MutableDict
不会 对字典内部的值本身应用可变跟踪。因此,它不足以解决跟踪递归字典结构(例如 JSON 结构)的深层更改的用例。要支持此用例,请构建一个 MutableDict
的子类,以提供适当的强制转换,以便放置在字典中的值也是“可变的”,并将事件传播到其父结构。
另请参见
MutableList
MutableSet
成员
clear(), coerce(), pop(), popitem(), setdefault(), update()
类签名
类sqlalchemy.ext.mutable.MutableDict
(sqlalchemy.ext.mutable.Mutable
, builtins.dict
, typing.Generic
)
method clear() → None. Remove all items from D.
代码语言:javascript复制classmethod coerce(key: str, value: Any) → MutableDict[_KT, _VT] | None
将普通字典转换为此类的实例。
代码语言:javascript复制method pop(k[, d]) → v, remove specified key and return the corresponding value.
如果找不到键,则返回默认值(如果给定);否则,引发 KeyError。
代码语言:javascript复制method popitem() → Tuple[_KT, _VT]
移除并返回一个(key, value)对作为 2 元组。
键值对以 LIFO(后进先出)顺序返回。如果字典为空,则引发 KeyError。
代码语言:javascript复制method setdefault(*arg)
如果键不在字典中,则将键插入并设置默认值。
如果键在字典中,则返回键的值,否则返回默认值。
代码语言:javascript复制method update([E, ]**F) → None. Update D from dict/iterable E and F.
如果 E 存在并且具有.keys()方法,则执行: for k in E: D[k] = E[k] 如果 E 存在但缺少.keys()方法,则执行: for k, v in E: D[k] = v 在任一情况下,接下来执行: for k in F: D[k] = F[k]
代码语言:javascript复制class sqlalchemy.ext.mutable.MutableList
一个实现了Mutable
的列表类型。
MutableList
对象实现了一个列表,当列表的内容被更改时,包括添加或删除值时,将向底层映射发送更改事件。
请注意,MutableList
不会对列表内部的值本身应用可变跟踪。因此,它不是跟踪对递归可变结构进行深层更改的使用案例的充分解决方案,例如 JSON 结构。为支持此使用案例,请构建MutableList
的子类,该子类提供适当的强制转换以使放置在字典中的值也是“可变的”,并将事件发送到其父结构。
另请参阅
MutableDict
MutableSet
成员
append(), clear(), coerce(), extend(), insert(), is_iterable(), is_scalar(), pop(), remove(), reverse(), sort()
类签名
类sqlalchemy.ext.mutable.MutableList
(sqlalchemy.ext.mutable.Mutable
,builtins.list
,typing.Generic
)
method append(x: _T) → None
将对象追加到列表末尾。
代码语言:javascript复制method clear() → None
从列表中删除所有项。
代码语言:javascript复制classmethod coerce(key: str, value: MutableList[_T] | _T) → MutableList[_T] | None
将普通列表转换为此类的实例。
代码语言:javascript复制method extend(x: Iterable[_T]) → None
通过将来自可迭代对象的元素附加到列表来扩展列表。
代码语言:javascript复制method insert(i: SupportsIndex, x: _T) → None
在索引之前插入对象。
代码语言:javascript复制method is_iterable(value: _T | Iterable[_T]) → TypeGuard[Iterable[_T]]
代码语言:javascript复制method is_scalar(value: _T | Iterable[_T]) → TypeGuard[_T]
代码语言:javascript复制method pop(*arg: SupportsIndex) → _T
删除并返回索引处的项(默认为最后一个)。
如果列表为空或索引超出范围,则引发 IndexError。
代码语言:javascript复制method remove(i: _T) → None
删除值的第一个出现。
如果值不存在,则引发 ValueError。
代码语言:javascript复制method reverse() → None
就地反转。
代码语言:javascript复制method sort(**kw: Any) → None
将列表按升序排序并返回 None。
排序是原地进行的(即列表本身被修改)并且稳定的(即保持两个相等元素的顺序不变)。
如果给定了键函数,则将其应用于每个列表项一次,并根据其函数值按升序或降序对它们进行排序。
反转标志可以设置为按降序排序。
代码语言:javascript复制class sqlalchemy.ext.mutable.MutableSet
实现了Mutable
的集合类型。
MutableSet
对象实现了一个集合,当集合的内容发生变化时,包括添加或移除值时,会向底层映射发送更改事件。
注意,MutableSet
不会对集合内部的值本身应用可变跟踪。因此,它不是跟踪对递归可变结构进行深层更改的足够解决方案。为了支持这种用例,构建一个MutableSet
的子类,提供适当的强制转换,以便放置在字典中的值也是“可变的”,并向它们的父结构发出事件。
另请参阅
MutableDict
MutableList
成员
add(), clear(), coerce(), difference_update(), discard(), intersection_update(), pop(), remove(), symmetric_difference_update(), update()
类签名
类sqlalchemy.ext.mutable.MutableSet
(sqlalchemy.ext.mutable.Mutable
, builtins.set
, typing.Generic
)
method add(elem: _T) → None
向集合添加一个元素。
如果元素已经存在,则不产生任何效果。
代码语言:javascript复制method clear() → None
从此集合中移除所有元素。
代码语言:javascript复制classmethod coerce(index: str, value: Any) → MutableSet[_T] | None
将普通集合转换为此类的实例。
代码语言:javascript复制method difference_update(*arg: Iterable[Any]) → None
从此集合中移除另一个集合的所有元素。
代码语言:javascript复制method discard(elem: _T) → None
如果元素是集合的成员,则从集合中移除一个元素。
如果元素不是成员,则不执行任何操作。
代码语言:javascript复制method intersection_update(*arg: Iterable[Any]) → None
使用自身和另一个集合的交集更新集合。
代码语言:javascript复制method pop(*arg: Any) → _T
移除并返回任意集合元素。如果集合为空,则引发 KeyError。
代码语言:javascript复制method remove(elem: _T) → None
从集合中移除一个元素;它必须是成员。
如果元素不是成员,则引发 KeyError。
代码语言:javascript复制method symmetric_difference_update(*arg: Iterable[_T]) → None
使用自身和另一个集合的对称差集更新集合。
代码语言:javascript复制method update(*arg: Iterable[_T]) → None
使用自身和其他集合的并集更新集合。 tableList
代码语言:javascript复制一个实现了`Mutable`的列表类型。
`MutableList` 对象实现了一个列表,当列表的内容被更改时,包括添加或删除值时,将向底层映射发送更改事件。
请注意,`MutableList` 不会对列表内部的*值本身*应用可变跟踪。因此,它不是跟踪对*递归*可变结构进行深层更改的使用案例的充分解决方案,例如 JSON 结构。为支持此使用案例,请构建`MutableList`的子类,该子类提供适当的强制转换以使放置在字典中的值也是“可变的”,并将事件发送到其父结构。
另请参阅
`MutableDict`
`MutableSet`
**成员**
append(), clear(), coerce(), extend(), insert(), is_iterable(), is_scalar(), pop(), remove(), reverse(), sort()
**类签名**
类`sqlalchemy.ext.mutable.MutableList`(`sqlalchemy.ext.mutable.Mutable`,`builtins.list`,`typing.Generic`)
```py
method append(x: _T) → None
将对象追加到列表末尾。
代码语言:javascript复制method clear() → None
从列表中删除所有项。
代码语言:javascript复制classmethod coerce(key: str, value: MutableList[_T] | _T) → MutableList[_T] | None
将普通列表转换为此类的实例。
代码语言:javascript复制method extend(x: Iterable[_T]) → None
通过将来自可迭代对象的元素附加到列表来扩展列表。
代码语言:javascript复制method insert(i: SupportsIndex, x: _T) → None
在索引之前插入对象。
代码语言:javascript复制method is_iterable(value: _T | Iterable[_T]) → TypeGuard[Iterable[_T]]
代码语言:javascript复制method is_scalar(value: _T | Iterable[_T]) → TypeGuard[_T]
代码语言:javascript复制method pop(*arg: SupportsIndex) → _T
删除并返回索引处的项(默认为最后一个)。
如果列表为空或索引超出范围,则引发 IndexError。
代码语言:javascript复制method remove(i: _T) → None
删除值的第一个出现。
如果值不存在,则引发 ValueError。
代码语言:javascript复制method reverse() → None
就地反转。
代码语言:javascript复制method sort(**kw: Any) → None
将列表按升序排序并返回 None。
排序是原地进行的(即列表本身被修改)并且稳定的(即保持两个相等元素的顺序不变)。
如果给定了键函数,则将其应用于每个列表项一次,并根据其函数值按升序或降序对它们进行排序。
反转标志可以设置为按降序排序。
代码语言:javascript复制class sqlalchemy.ext.mutable.MutableSet
实现了Mutable
的集合类型。
MutableSet
对象实现了一个集合,当集合的内容发生变化时,包括添加或移除值时,会向底层映射发送更改事件。
注意,MutableSet
不会对集合内部的值本身应用可变跟踪。因此,它不是跟踪对递归可变结构进行深层更改的足够解决方案。为了支持这种用例,构建一个MutableSet
的子类,提供适当的强制转换,以便放置在字典中的值也是“可变的”,并向它们的父结构发出事件。
另请参阅
MutableDict
MutableList
成员
add(), clear(), coerce(), difference_update(), discard(), intersection_update(), pop(), remove(), symmetric_difference_update(), update()
类签名
类sqlalchemy.ext.mutable.MutableSet
(sqlalchemy.ext.mutable.Mutable
, builtins.set
, typing.Generic
)
method add(elem: _T) → None
向集合添加一个元素。
如果元素已经存在,则不产生任何效果。
代码语言:javascript复制method clear() → None
从此集合中移除所有元素。
代码语言:javascript复制classmethod coerce(index: str, value: Any) → MutableSet[_T] | None
将普通集合转换为此类的实例。
代码语言:javascript复制method difference_update(*arg: Iterable[Any]) → None
从此集合中移除另一个集合的所有元素。
代码语言:javascript复制method discard(elem: _T) → None
如果元素是集合的成员,则从集合中移除一个元素。
如果元素不是成员,则不执行任何操作。
代码语言:javascript复制method intersection_update(*arg: Iterable[Any]) → None
使用自身和另一个集合的交集更新集合。
代码语言:javascript复制method pop(*arg: Any) → _T
移除并返回任意集合元素。如果集合为空,则引发 KeyError。
代码语言:javascript复制method remove(elem: _T) → None
从集合中移除一个元素;它必须是成员。
如果元素不是成员,则引发 KeyError。
代码语言:javascript复制method symmetric_difference_update(*arg: Iterable[_T]) → None
使用自身和另一个集合的对称差集更新集合。
代码语言:javascript复制method update(*arg: Iterable[_T]) → None
使用自身和其他集合的并集更新集合。