集合自定义和 API 详情

函数 relationship() 定义了两个类之间的关联。当关联定义了一对多或多对多关系时,它在加载和操作对象时以 Python 集合的形式表示。本节介绍有关集合配置和技术的附加信息。

自定义集合访问

映射一对多或多对多关系会导致一个集合,可以通过父实例上的属性访问该集合。这两种关系中最常见的集合类型是 listset,在使用 声明式 映射的 Mapped 中,可以通过在 Mapped 容器内使用集合类型来建立,如下面的 Parent.children 集合中使用了 list

from sqlalchemy import ForeignKey

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class Parent(Base):
    __tablename__ = "parent"

    parent_id: Mapped[int] = mapped_column(primary_key=True)

    # use a list
    children: Mapped[List["Child"]] = relationship()


class Child(Base):
    __tablename__ = "child"

    child_id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))

或者使用 set,在相同的 Parent.children 集合中进行说明

from typing import Set
from sqlalchemy import ForeignKey

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class Parent(Base):
    __tablename__ = "parent"

    parent_id: Mapped[int] = mapped_column(primary_key=True)

    # use a set
    children: Mapped[Set["Child"]] = relationship()


class Child(Base):
    __tablename__ = "child"

    child_id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))

注意

如果使用 Python 3.7 或 3.8,集合的注释需要使用 typing.Listtyping.Set,例如 Mapped[List["Child"]]Mapped[Set["Child"]];这些 Python 版本中的 Python 内置函数 listset 还不支持泛型注释,例如

from typing import List


class Parent(Base):
    __tablename__ = "parent"

    parent_id: Mapped[int] = mapped_column(primary_key=True)

    # use a List, Python 3.8 and earlier
    children: Mapped[List["Child"]] = relationship()

在不使用 Mapped 注释的情况下,例如使用 命令式映射 或未类型化的 Python 代码,以及在一些特殊情况下,始终可以使用参数 relationship.collection_class 直接指定 relationship() 的集合类

# non-annotated mapping


class Parent(Base):
    __tablename__ = "parent"

    parent_id = mapped_column(Integer, primary_key=True)

    children = relationship("Child", collection_class=set)


class Child(Base):
    __tablename__ = "child"

    child_id = mapped_column(Integer, primary_key=True)
    parent_id = mapped_column(ForeignKey("parent.id"))

在没有 relationship.collection_classMapped 的情况下,默认集合类型为 list

除了 listset 内置函数之外,还支持两种字典,在下面的 字典集合 中介绍。还支持任何任意可变序列类型都可以设置为目标集合,只需要一些额外的配置步骤;这在 自定义集合实现 部分进行介绍。

字典集合

使用字典作为集合时,需要一些额外的细节。这是因为对象总是从数据库中加载为列表,并且必须有一个键生成策略才能正确地填充字典。函数 attribute_keyed_dict() 是迄今为止实现简单字典集合最常见的方法。它生成一个字典类,该类将映射类的特定属性作为键。下面,我们将映射一个包含 Note 项的字典的 Item 类,该字典以 Note.keyword 属性为键。使用 attribute_keyed_dict() 时,可以使用 KeyFuncDict 或简单地使用 dict 来对 Mapped 注释进行类型化,如下面的示例所示。但是,在这种情况下,需要使用参数 relationship.collection_class,以便可以对 attribute_keyed_dict() 进行适当的参数化

from typing import Dict
from typing import Optional

from sqlalchemy import ForeignKey
from sqlalchemy.orm import attribute_keyed_dict
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class Item(Base):
    __tablename__ = "item"

    id: Mapped[int] = mapped_column(primary_key=True)

    notes: Mapped[Dict[str, "Note"]] = relationship(
        collection_class=attribute_keyed_dict("keyword"),
        cascade="all, delete-orphan",
    )


class Note(Base):
    __tablename__ = "note"

    id: Mapped[int] = mapped_column(primary_key=True)
    item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
    keyword: Mapped[str]
    text: Mapped[Optional[str]]

    def __init__(self, keyword: str, text: str):
        self.keyword = keyword
        self.text = text

然后 Item.notes 是一个字典

>>> item = Item()
>>> item.notes["a"] = Note("a", "atext")
>>> item.notes.items()
{'a': <__main__.Note object at 0x2eaaf0>}

attribute_keyed_dict() 将确保每个 Note.keyword 属性与字典中的键相匹配。例如,当为 Item.notes 赋值时,我们提供的字典键必须与实际的 Note 对象的键相匹配

item = Item()
item.notes = {
    "a": Note("a", "atext"),
    "b": Note("b", "btext"),
}

attribute_keyed_dict() 使用的属性作为键,它不需要被映射!使用普通的 Python @property 允许使用几乎任何关于对象的细节或细节组合作为键,就像下面我们将它设置为一个包含 Note.keywordNote.text 字段的前十个字母的元组一样

class Item(Base):
    __tablename__ = "item"

    id: Mapped[int] = mapped_column(primary_key=True)

    notes: Mapped[Dict[str, "Note"]] = relationship(
        collection_class=attribute_keyed_dict("note_key"),
        back_populates="item",
        cascade="all, delete-orphan",
    )


class Note(Base):
    __tablename__ = "note"

    id: Mapped[int] = mapped_column(primary_key=True)
    item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
    keyword: Mapped[str]
    text: Mapped[str]

    item: Mapped["Item"] = relationship()

    @property
    def note_key(self):
        return (self.keyword, self.text[0:10])

    def __init__(self, keyword: str, text: str):
        self.keyword = keyword
        self.text = text

上面我们添加了一个 Note.item 关系,它具有一个双向的 relationship.back_populates 配置。分配给这个反向关系,Note 会被添加到 Item.notes 字典中,键会自动生成

>>> item = Item()
>>> n1 = Note("a", "atext")
>>> n1.item = item
>>> item.notes
{('a', 'atext'): <__main__.Note object at 0x2eaaf0>}

其他内置字典类型包括 column_keyed_dict(),它几乎与 attribute_keyed_dict() 相同,除了直接给出 Column 对象

from sqlalchemy.orm import column_keyed_dict


class Item(Base):
    __tablename__ = "item"

    id: Mapped[int] = mapped_column(primary_key=True)

    notes: Mapped[Dict[str, "Note"]] = relationship(
        collection_class=column_keyed_dict(Note.__table__.c.keyword),
        cascade="all, delete-orphan",
    )

以及 mapped_collection(),它接收任何可调用函数。请注意,通常使用 attribute_keyed_dict() 以及前面提到的 @property 更容易

from sqlalchemy.orm import mapped_collection


class Item(Base):
    __tablename__ = "item"

    id: Mapped[int] = mapped_column(primary_key=True)

    notes: Mapped[Dict[str, "Note"]] = relationship(
        collection_class=mapped_collection(lambda note: note.text[0:10]),
        cascade="all, delete-orphan",
    )

字典映射通常与“Association Proxy”扩展结合使用,以生成简化的字典视图。请参阅 代理到基于字典的集合复合关联代理 获取示例。

处理字典集合中的键突变和反向填充

使用 attribute_keyed_dict() 时,字典的“键”是从目标对象上的属性中获取的。对这个键的更改不会被跟踪。这意味着键必须在第一次使用时被分配,如果键发生改变,集合就不会被突变。一个典型的示例是依赖于反向引用来填充映射属性集合。以下是

class A(Base):
    __tablename__ = "a"

    id: Mapped[int] = mapped_column(primary_key=True)

    bs: Mapped[Dict[str, "B"]] = relationship(
        collection_class=attribute_keyed_dict("data"),
        back_populates="a",
    )


class B(Base):
    __tablename__ = "b"

    id: Mapped[int] = mapped_column(primary_key=True)
    a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
    data: Mapped[str]

    a: Mapped["A"] = relationship(back_populates="bs")

上面,如果我们创建一个引用特定 A()B(),反向填充就会将 B() 添加到 A.bs 集合中,但是如果 B.data 的值还没有设置,键将是 None

>>> a1 = A()
>>> b1 = B(a=a1)
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}

事后设置 b1.data 不会更新集合

>>> b1.data = "the key"
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}

如果尝试在构造函数中设置 B(),也可以看到这一点。参数的顺序会改变结果

>>> B(a=a1, data="the key")
<test3.B object at 0x7f7b10114280>
>>> a1.bs
{None: <test3.B object at 0x7f7b10114280>}

vs

>>> B(data="the key", a=a1)
<test3.B object at 0x7f7b10114340>
>>> a1.bs
{'the key': <test3.B object at 0x7f7b10114340>}

如果以这种方式使用反向引用,请确保使用 __init__ 方法按正确的顺序填充属性。

以下事件处理程序也可以用于跟踪集合中的更改

from sqlalchemy import event
from sqlalchemy.orm import attributes


@event.listens_for(B.data, "set")
def set_item(obj, value, previous, initiator):
    if obj.a is not None:
        previous = None if previous == attributes.NO_VALUE else previous
        obj.a.bs[value] = obj
        obj.a.bs.pop(previous)

自定义集合实现

您也可以对集合使用自己的类型。在简单的情况下,从 listset 继承,添加自定义行为,就足够了。在其他情况下,需要特殊的装饰器来告诉 SQLAlchemy 集合如何操作的更多细节。

SQLAlchemy 中的集合是透明地受控的。控制意味着集合上的正常操作会被跟踪,并在刷新时写入数据库。此外,集合操作可以触发事件,指示必须执行某些辅助操作。辅助操作的示例包括将子项保存在父项的 Session 中(即 save-update 级联),以及同步双向关系的状态(即 backref())。

collections 包了解列表、集合和字典的基本接口,并将自动将控制应用于这些内置类型及其子类。实现了基本集合接口的对象派生类型通过鸭子类型检测和控制

class ListLike:
    def __init__(self):
        self.data = []

    def append(self, item):
        self.data.append(item)

    def remove(self, item):
        self.data.remove(item)

    def extend(self, items):
        self.data.extend(items)

    def __iter__(self):
        return iter(self.data)

    def foo(self):
        return "foo"

appendremoveextendlist 的已知成员,将自动被控制。 __iter__ 不是修改器方法,不会被控制,foo 也不会。

当然,鸭子类型(即猜测)并不牢不可破,因此您可以通过提供 __emulates__ 类属性来明确您正在实现的接口

class SetLike:
    __emulates__ = set

    def __init__(self):
        self.data = set()

    def append(self, item):
        self.data.add(item)

    def remove(self, item):
        self.data.remove(item)

    def __iter__(self):
        return iter(self.data)

这个类看起来类似于 Python list(即“列表状”),因为它有一个 append 方法,但 __emulates__ 属性强制它被视为一个 setremove 被认为是集合接口的一部分,并将被控制。

但这个类还不能工作:需要一些粘合剂来适应 SQLAlchemy 的使用。ORM 需要知道使用哪些方法来追加、删除和迭代集合的成员。当使用像 listset 这样的类型时,适当的方法是众所周知的,并且在存在时会被自动使用。但是上面的类,它只粗略地类似于 set,没有提供预期的 add 方法,因此我们必须指示 ORM 使用哪个方法来代替 add 方法,在本例中使用装饰器 @collection.appender;这将在下一节中说明。

通过装饰器注释自定义集合

装饰器可以用来标记 ORM 管理集合所需的单个方法。当您的类不完全符合其容器类型的常规接口时,或者当您希望使用其他方法来完成任务时,可以使用它们。

from sqlalchemy.orm.collections import collection


class SetLike:
    __emulates__ = set

    def __init__(self):
        self.data = set()

    @collection.appender
    def append(self, item):
        self.data.add(item)

    def remove(self, item):
        self.data.remove(item)

    def __iter__(self):
        return iter(self.data)

这就是完成示例所需的全部内容。SQLAlchemy 将通过 append 方法添加实例。 remove__iter__ 是集合的默认方法,将用于删除和迭代。默认方法也可以更改

from sqlalchemy.orm.collections import collection


class MyList(list):
    @collection.remover
    def zark(self, item):
        # do something special...
        ...

    @collection.iterator
    def hey_use_this_instead_for_iteration(self): ...

完全没有必要是“列表式”或“集合式”。只要拥有 SQLAlchemy 用途标记的 append、remove 和迭代接口,集合类就可以是任何形状。append 和 remove 方法将以映射实体作为唯一参数调用,而迭代器方法则以无参数调用,必须返回一个迭代器。

自定义字典式集合

The KeyFuncDict 类可以用作自定义类型的基类或作为 mix-in,以快速将 dict 集合支持添加到其他类中。它使用键值函数来委托给 __setitem____delitem__

from sqlalchemy.orm.collections import KeyFuncDict


class MyNodeMap(KeyFuncDict):
    """Holds 'Node' objects, keyed by the 'name' attribute."""

    def __init__(self, *args, **kw):
        super().__init__(keyfunc=lambda node: node.name)
        dict.__init__(self, *args, **kw)

当子类化 KeyFuncDict 时,如果用户定义的 __setitem__()__delitem__() 版本调用了 KeyFuncDict 上的相同方法,则应使用 collection.internally_instrumented() 来装饰它们。这是因为 KeyFuncDict 上的方法已经被检测了 - 在已经检测过的调用中调用它们会导致事件被反复或不适当地触发,从而在极少数情况下导致内部状态损坏。

from sqlalchemy.orm.collections import KeyFuncDict, collection


class MyKeyFuncDict(KeyFuncDict):
    """Use @internally_instrumented when your methods
    call down to already-instrumented methods.

    """

    @collection.internally_instrumented
    def __setitem__(self, key, value, _sa_initiator=None):
        # do something with key, value
        super(MyKeyFuncDict, self).__setitem__(key, value, _sa_initiator)

    @collection.internally_instrumented
    def __delitem__(self, key, _sa_initiator=None):
        # do something with key
        super(MyKeyFuncDict, self).__delitem__(key, _sa_initiator)

ORM 理解 dict 接口,就像列表和集合一样,如果选择子类化 dict 或在鸭子类型类中提供类似字典的集合行为,它将自动检测所有“类似字典”的方法。但是,您必须装饰追加器和移除器方法 - 基本字典接口中没有 SQLAlchemy 可以默认使用的兼容方法。迭代将通过 values() 进行,除非另有装饰。

检测和自定义类型

许多自定义类型和现有的库类可以直接用作实体集合类型,无需进一步操作。但是,重要的是要注意,检测过程将修改类型,在方法周围自动添加装饰器。

这些装饰器很轻量级,在关系之外是无操作的,但它们在其他地方触发时会增加不必要的开销。当使用库类作为集合时,最好使用“平凡子类”技巧,将装饰器限制在关系中的使用中。例如

class MyAwesomeList(some.great.library.AwesomeList):
    pass


# ... relationship(..., collection_class=MyAwesomeList)

ORM 对内置类型使用此方法,当直接使用 listsetdict 时,会默默地替换一个平凡子类。

集合 API

对象名称 描述

attribute_keyed_dict(attr_name, *, [ignore_unpopulated_attribute])

一种基于属性键值的字典式集合类型。

attribute_mapped_collection

一种基于属性键值的字典式集合类型。

column_keyed_dict(mapping_spec, *, [ignore_unpopulated_attribute])

一种基于列键值的字典式集合类型。

column_mapped_collection

一种基于列键值的字典式集合类型。

keyfunc_mapping(keyfunc, *, [ignore_unpopulated_attribute])

一种具有任意键值的字典式集合类型。

KeyFuncDict

ORM 映射字典类的基类。

mapped_collection

一种具有任意键值的字典式集合类型。

MappedCollection

ORM 映射字典类的基类。

function sqlalchemy.orm.attribute_keyed_dict(attr_name: str, *, ignore_unpopulated_attribute: bool = False) Type[KeyFuncDict[Any, Any]]

一种基于属性键值的字典式集合类型。

Changed in version 2.0: attribute_mapped_collection 重命名为 attribute_keyed_dict()

返回一个 KeyFuncDict 工厂,该工厂将根据 ORM 映射实例上特定命名属性的值生成新的字典键,这些实例将被添加到字典中。

注意

目标属性的值必须在对象被添加到字典集合时与其值一起赋值。此外,不会跟踪对键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关更多详细信息,请参阅 处理字典集合的键突变和反向填充

另请参阅

字典集合 - 使用背景

参数:
  • attr_name – 映射类上 ORM 映射属性的字符串名称,该属性的值将在特定实例上用作该实例的新字典条目的键。

  • ignore_unpopulated_attribute

    如果为 True,并且对象上的目标属性根本没有填充,则操作将被静默跳过。默认情况下,会引发错误。

    New in version 2.0: 如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。可以设置 attribute_keyed_dict.ignore_unpopulated_attribute 参数,这将表明应忽略此条件,并且 append 操作将静默跳过。这与 1.x 系列的行为形成对比,1.x 系列会错误地在字典中填充一个值为 None 的任意键值。

function sqlalchemy.orm.column_keyed_dict(mapping_spec: Type[_KT] | Callable[[_KT], _VT], *, ignore_unpopulated_attribute: bool = False) Type[KeyFuncDict[_KT, _KT]]

一种基于列键值的字典式集合类型。

Changed in version 2.0: column_mapped_collection 重命名为 column_keyed_dict

返回一个 KeyFuncDict 工厂,该工厂将根据 ORM 映射实例上特定 Column 映射属性的值生成新的字典键,这些实例将被添加到字典中。

注意

目标属性的值必须在对象被添加到字典集合时与其值一起赋值。此外,不会跟踪对键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关更多详细信息,请参阅 处理字典集合的键突变和反向填充

另请参阅

字典集合 - 使用背景

参数:
  • mapping_spec – 一个 Column 对象,目标映射器期望将该对象映射到映射类上的特定属性,该属性在特定实例上的值将用作该实例的新字典条目的键。

  • ignore_unpopulated_attribute

    如果为 True,并且给定 Column 目标属性指示的对象上的映射属性根本没有填充,则操作将被静默跳过。默认情况下,会引发错误。

    版本 2.0 中的新功能: 如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。可以设置 column_keyed_dict.ignore_unpopulated_attribute 参数,该参数将指示应忽略此条件,并静默跳过追加操作。这与 1.x 系列的行为形成对比,后者会错误地将字典中的值填充为任意键值 None

function sqlalchemy.orm.keyfunc_mapping(keyfunc: Callable[[Any], Any], *, ignore_unpopulated_attribute: bool = False) Type[KeyFuncDict[_KT, Any]]

一种具有任意键值的字典式集合类型。

版本 2.0 中的变更: mapped_collection 重命名为 keyfunc_mapping().

返回一个 KeyFuncDict 工厂,该工厂具有从 keyfunc 生成的键控函数,该函数是一个可调用对象,它接受一个实体并返回一个键值。

注意

给定的 keyfunc 仅在目标对象被添加到集合时调用一次。函数返回的有效值的更改不会被跟踪。

另请参阅

字典集合 - 使用背景

参数:
  • keyfunc – 一个可调用对象,该对象将传递 ORM 映射的实例,然后该实例应生成一个新的键以在字典中使用。如果返回的值是 LoaderCallableStatus.NO_VALUE,则会引发错误。

  • ignore_unpopulated_attribute

    如果为 True,并且可调用对象为特定实例返回 LoaderCallableStatus.NO_VALUE,则操作将被静默跳过。默认情况下,会引发错误。

    版本 2.0 中的新功能: 如果用于字典键的可调用对象返回 LoaderCallableStatus.NO_VALUE,则默认情况下会引发错误,在 ORM 属性上下文中,这表示从未填充任何值的属性。可以设置 mapped_collection.ignore_unpopulated_attribute 参数,该参数将指示应忽略此条件,并静默跳过追加操作。这与 1.x 系列的行为形成对比,后者会错误地将字典中的值填充为任意键值 None

sqlalchemy.orm.attribute_mapped_collection = <function attribute_keyed_dict>

一种基于属性键值的字典式集合类型。

Changed in version 2.0: attribute_mapped_collection 重命名为 attribute_keyed_dict()

返回一个 KeyFuncDict 工厂,该工厂将根据 ORM 映射实例上特定命名属性的值生成新的字典键,这些实例将被添加到字典中。

注意

目标属性的值必须在对象被添加到字典集合时与其值一起赋值。此外,不会跟踪对键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关更多详细信息,请参阅 处理字典集合的键突变和反向填充

另请参阅

字典集合 - 使用背景

参数:
  • attr_name – 映射类上 ORM 映射属性的字符串名称,该属性在特定实例上的值将用作该实例的新字典条目的键。

  • ignore_unpopulated_attribute

    如果为 True,并且对象上的目标属性根本没有填充,则操作将被静默跳过。默认情况下,会引发错误。

    New in version 2.0: 如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。可以设置 attribute_keyed_dict.ignore_unpopulated_attribute 参数,这将表明应忽略此条件,并且 append 操作将静默跳过。这与 1.x 系列的行为形成对比,1.x 系列会错误地在字典中填充一个值为 None 的任意键值。

sqlalchemy.orm.column_mapped_collection = <function column_keyed_dict>

一种基于列键值的字典式集合类型。

Changed in version 2.0: column_mapped_collection 重命名为 column_keyed_dict

返回一个 KeyFuncDict 工厂,该工厂将根据 ORM 映射实例上特定 Column 映射属性的值生成新的字典键,这些实例将被添加到字典中。

注意

目标属性的值必须在对象被添加到字典集合时与其值一起赋值。此外,不会跟踪对键属性的更改,这意味着字典中的键不会自动与目标对象本身的键值同步。有关更多详细信息,请参阅 处理字典集合的键突变和反向填充

另请参阅

字典集合 - 使用背景

参数:
  • mapping_spec – 一个 Column 对象,目标映射器期望将该对象映射到映射类上的特定属性,该属性在特定实例上的值将用作该实例的新字典条目的键。

  • ignore_unpopulated_attribute

    如果为 True,并且给定 Column 目标属性指示的对象上的映射属性根本没有填充,则操作将被静默跳过。默认情况下,会引发错误。

    版本 2.0 中的新功能: 如果确定用于字典键的属性从未填充任何值,则默认情况下会引发错误。可以设置 column_keyed_dict.ignore_unpopulated_attribute 参数,该参数将指示应忽略此条件,并静默跳过追加操作。这与 1.x 系列的行为形成对比,后者会错误地将字典中的值填充为任意键值 None

sqlalchemy.orm.mapped_collection = <function keyfunc_mapping>

一种具有任意键值的字典式集合类型。

版本 2.0 中的变更: mapped_collection 重命名为 keyfunc_mapping().

返回一个 KeyFuncDict 工厂,该工厂具有从 keyfunc 生成的键控函数,该函数是一个可调用对象,它接受一个实体并返回一个键值。

注意

给定的 keyfunc 仅在目标对象被添加到集合时调用一次。函数返回的有效值的更改不会被跟踪。

另请参阅

字典集合 - 使用背景

参数:
  • keyfunc – 一个可调用对象,该对象将传递 ORM 映射的实例,然后该实例应生成一个新的键以在字典中使用。如果返回的值是 LoaderCallableStatus.NO_VALUE,则会引发错误。

  • ignore_unpopulated_attribute

    如果为 True,并且可调用对象为特定实例返回 LoaderCallableStatus.NO_VALUE,则操作将被静默跳过。默认情况下,会引发错误。

    版本 2.0 中的新功能: 如果用于字典键的可调用对象返回 LoaderCallableStatus.NO_VALUE,则默认情况下会引发错误,在 ORM 属性上下文中,这表示从未填充任何值的属性。可以设置 mapped_collection.ignore_unpopulated_attribute 参数,该参数将指示应忽略此条件,并静默跳过追加操作。这与 1.x 系列的行为形成对比,后者会错误地将字典中的值填充为任意键值 None

class sqlalchemy.orm.KeyFuncDict

ORM 映射字典类的基类。

用 SQLAlchemy ORM 集合类所需的附加方法扩展 dict 类型。使用 KeyFuncDict 最直接的方法是使用 attribute_keyed_dict()column_keyed_dict() 类工厂。 KeyFuncDict 也可以作为用户定义的自定义字典类的基础。

版本 2.0 中的变更: MappedCollection 重命名为 KeyFuncDict.

类签名

class sqlalchemy.orm.KeyFuncDict (builtins.dict, typing.Generic)

method sqlalchemy.orm.KeyFuncDict.__init__(keyfunc: Callable[[Any], Any], *dict_args: Any, ignore_unpopulated_attribute: bool = False) None

使用 keyfunc 提供的键控创建一个新集合。

keyfunc 可以是任何可调用对象,它接受一个对象并返回一个对象以用作字典键。

每次 ORM 需要按值添加成员(例如从数据库加载实例)或删除成员时,都会调用 keyfunc。字典键控的通常注意事项适用 - keyfunc(object) 应在集合的整个生命周期内返回相同的输出。基于可变属性的键控会导致无法访问的实例“丢失”在集合中。

方法 sqlalchemy.orm.KeyFuncDict.clear() None.  D 移除 所有 项。
方法 sqlalchemy.orm.KeyFuncDict.pop(k[, d]) v, 移除 指定 返回 对应的 值。

如果键未找到,则返回默认值(如果给出);否则,则引发 KeyError。

方法 sqlalchemy.orm.KeyFuncDict.popitem()

移除并返回一个 (键,值) 对作为 2 元组。

对以 LIFO(后进先出)顺序返回。如果字典为空,则引发 KeyError。

方法 sqlalchemy.orm.KeyFuncDict.remove(value: _KT, _sa_initiator: AttributeEventToken | Literal[None, False] = None) None

通过值移除项目,查询 keyfunc 获取键。

方法 sqlalchemy.orm.KeyFuncDict.set(value: _KT, _sa_initiator: AttributeEventToken | Literal[None, False] = None) None

通过值添加项目,查询 keyfunc 获取键。

方法 sqlalchemy.orm.KeyFuncDict.setdefault(key, default=None)

如果键不在字典中,则将键插入具有默认值的值。

如果键在字典中,则返回键的值,否则返回默认值。

方法 sqlalchemy.orm.KeyFuncDict.update([E, ]**F) None.  字典/可迭代对象 E F 更新 D

如果 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]

sqlalchemy.orm.MappedCollection = <class 'sqlalchemy.orm.mapped_collection.KeyFuncDict'>

ORM 映射字典类的基类。

用 SQLAlchemy ORM 集合类所需的附加方法扩展 dict 类型。使用 KeyFuncDict 最直接的方法是使用 attribute_keyed_dict()column_keyed_dict() 类工厂。 KeyFuncDict 也可以作为用户定义的自定义字典类的基础。

版本 2.0 中的变更: MappedCollection 重命名为 KeyFuncDict.

集合内部

对象名称 描述

bulk_replace(values, existing_adapter, new_adapter[, initiator])

加载新集合,根据之前的类似成员关系触发事件。

collection

实体集合类的装饰器。

collection_adapter

attrgetter(attr, …) –> attrgetter 对象

CollectionAdapter

ORM 与任意 Python 集合之间的桥梁。

InstrumentedDict

内置 dict 的工具版本。

InstrumentedList

内置 list 的工具版本。

InstrumentedSet

内置 set 的工具版本。

prepare_instrumentation(factory)

为将来用作集合类工厂准备一个可调用对象。

函数 sqlalchemy.orm.collections.bulk_replace(values, existing_adapter, new_adapter, initiator=None)

加载新集合,根据之前的类似成员关系触发事件。

values 中的实例附加到 new_adapter 上。将为 existing_adapter 中不存在的任何实例触发事件。对于 existing_adapter 中不存在于 values 中的任何实例,将对其触发移除事件。

参数:
sqlalchemy.orm.collections.collection

实体集合类的装饰器。

装饰器分为两组:注释和拦截配方。

注释装饰器(appender、remover、iterator、converter、internally_instrumented)指示方法的用途,不接受任何参数。它们没有用括号编写。

@collection.appender
def append(self, append): ...

配方装饰器都需要括号,即使那些不接受参数的装饰器也是如此。

@collection.adds('entity')
def insert(self, position, entity): ...

@collection.removes_return()
def popitem(self): ...
方法 sqlalchemy.orm.collections.collection.静态 adds(arg)

将方法标记为向集合添加实体。

在方法中添加“添加到集合”处理。装饰器参数指示哪个方法参数包含 SQLAlchemy 相关的值。参数可以按位置(即整数)或按名称指定。

@collection.adds(1)
def push(self, item): ...

@collection.adds('entity')
def do_stuff(self, thing, entity=None): ...
method sqlalchemy.orm.collections.collection.static appender(fn)

将该方法标记为集合追加器。

追加器方法使用一个位置参数调用:要追加的值。如果尚未装饰,该方法将自动使用“adds(1)”装饰。

@collection.appender
def add(self, append): ...

# or, equivalently
@collection.appender
@collection.adds(1)
def add(self, append): ...

# for mapping type, an 'append' may kick out a previous value
# that occupies that slot.  consider d['a'] = 'foo'- any previous
# value in d['a'] is discarded.
@collection.appender
@collection.replaces(1)
def add(self, entity):
    key = some_key_func(entity)
    previous = None
    if key in self:
        previous = self[key]
    self[key] = entity
    return previous

如果要追加的值在集合中不允许,则可能会引发异常。需要记住的是,将为数据库查询映射的每个对象调用追加器。如果数据库包含违反集合语义的行,您将需要想出创造性的方法来解决问题,因为通过集合进行访问将不起作用。

如果追加器方法在内部被检测,您还必须接收关键字参数“_sa_initiator”,并确保将其传播到集合事件。

method sqlalchemy.orm.collections.collection.static converter(fn)

将该方法标记为集合转换器。

从版本 1.3 起已弃用: collection.converter() 处理程序已弃用,将在未来版本中删除。请参考 bulk_replace 监听器接口,并结合 listen() 函数。

当集合被完全替换时,将调用此可选方法,例如

myobj.acollection = [newvalue1, newvalue2]

转换器方法将接收被分配的对象,并应返回适合 appender 方法使用的值的可迭代对象。转换器不得分配值或修改集合,其唯一任务是将用户提供的值调整为 ORM 可使用的值的可迭代对象。

默认转换器实现将使用鸭子类型来执行转换。类似字典的集合将转换为字典值的迭代器,其他类型将简单地进行迭代。

@collection.converter
def convert(self, other): ...

如果对象的鸭子类型与该集合的类型不匹配,则会引发 TypeError。

如果您想扩展可以批量分配的可能类型的范围或对将要分配的值执行验证,请提供此方法的实现。

method sqlalchemy.orm.collections.collection.static internally_instrumented(fn)

将该方法标记为检测。

此标记将阻止对该方法应用任何装饰。如果您在基本 SQLAlchemy 接口方法之一中协调您对 collection_adapter() 的调用,或者要阻止自动 ABC 方法装饰包装您的实现,请使用此标记。

# normally an 'extend' method on a list-like class would be
# automatically intercepted and re-implemented in terms of
# SQLAlchemy events and append().  your implementation will
# never be called, unless:
@collection.internally_instrumented
def extend(self, items): ...
method sqlalchemy.orm.collections.collection.static iterator(fn)

将该方法标记为集合删除器。

迭代器方法不带参数调用。预计它将返回所有集合成员的迭代器。

@collection.iterator
def __iter__(self): ...
method sqlalchemy.orm.collections.collection.static remover(fn)

将该方法标记为集合删除器。

删除器方法使用一个位置参数调用:要删除的值。如果尚未装饰,该方法将自动使用 removes_return() 装饰。

@collection.remover
def zap(self, entity): ...

# or, equivalently
@collection.remover
@collection.removes_return()
def zap(self, ): ...

如果要删除的值不在集合中,则可以引发异常或返回 None 来忽略错误。

如果删除方法在内部被检测,您还必须接收关键字参数“_sa_initiator”,并确保将其传播到集合事件。

method sqlalchemy.orm.collections.collection.static removes(arg)

将该方法标记为从集合中删除实体。

在方法中添加“从集合中删除”处理。装饰器参数指示哪个方法参数包含要删除的 SQLAlchemy 相关的值。参数可以按位置(即整数)或按名称指定。

@collection.removes(1)
def zap(self, item): ...

对于在调用时不知道要删除的值的方法,请使用 collection.removes_return。

method sqlalchemy.orm.collections.collection.static removes_return()

将该方法标记为从集合中删除实体。

在方法中添加“从集合中删除”处理。方法的返回值(如果有)被视为要删除的值。不会检查方法参数。

@collection.removes_return()
def pop(self): ...

对于在调用时知道要删除的值的方法,请使用 collection.remove。

method sqlalchemy.orm.collections.collection.static replaces(arg)

将该方法标记为替换集合中的实体。

在方法中添加“添加到集合”和“从集合中删除”处理。装饰器参数指示哪个方法参数包含要添加的 SQLAlchemy 相关的值,返回值(如果有)将被视为要删除的值。

参数可以按位置(即整数)或按名称指定。

@collection.replaces(2)
def __setitem__(self, index, item): ...
sqlalchemy.orm.collections.collection_adapter = operator.attrgetter('_sa_adapter')

attrgetter(attr, …) –> attrgetter 对象

返回一个可调用对象,该对象从其操作数中获取给定的属性。在 f = attrgetter('name') 之后,调用 f(r) 将返回 r.name。在 g = attrgetter('name', 'date') 之后,调用 g(r) 将返回 (r.name, r.date)。在 h = attrgetter('name.first', 'name.last') 之后,调用 h(r) 将返回 (r.name.first, r.name.last)。

class sqlalchemy.orm.collections.CollectionAdapter

ORM 与任意 Python 集合之间的桥梁。

将基本级集合操作(追加、删除、迭代)代理到底层 Python 集合,并为进入或离开集合的实体发出添加/删除事件。

ORM 专门使用 CollectionAdapter 与实体集合进行交互。

class sqlalchemy.orm.collections.InstrumentedDict

内置 dict 的工具版本。

类签名

class sqlalchemy.orm.collections.InstrumentedDict (builtins.dict, typing.Generic)

class sqlalchemy.orm.collections.InstrumentedList

内置 list 的工具版本。

类签名

class sqlalchemy.orm.collections.InstrumentedList (builtins.list, typing.Generic)

class sqlalchemy.orm.collections.InstrumentedSet

内置 set 的工具版本。

类签名

class sqlalchemy.orm.collections.InstrumentedSet (builtins.set, typing.Generic)

function sqlalchemy.orm.collections.prepare_instrumentation(factory: Type[Collection[Any]] | Callable[[], _AdaptedCollectionProtocol]) Callable[[], _AdaptedCollectionProtocol]

为将来用作集合类工厂准备一个可调用对象。

给定一个集合类工厂(类型或无参数可调用对象),返回另一个工厂,该工厂在调用时将生成兼容的实例。

此函数负责将 collection_class=list 转换为 collection_class=InstrumentedList 的运行时行为。