集合定制和 API 详情

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

定制集合访问

映射一对多或多对多关系会在父实例的属性中产生一个可访问的值集合。这两种常见的集合类型是 listset,在 Declarative 映射中使用 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"]]listset Python 内置类型在这些 Python 版本中尚不支持泛型注解,例如

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() 的集合类始终可以使用 relationship.collection_class 参数直接指定

# 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() 函数是实现简单字典集合的最常用方法。它生成一个字典类,该类将应用映射类的特定属性作为键。下面我们映射一个 Item 类,该类包含一个 Note 项的字典,这些项的键是 Note.keyword 属性。当使用 attribute_keyed_dict() 时,Mapped 注解可以使用 KeyFuncDict 或普通 dict 进行类型标注,如下例所示。但是,在这种情况下,需要 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",
    )

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

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

当使用 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>}

对比

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

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

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 的已知成员,并将自动进行 instrumentation。__iter__ 不是 mutator 方法,不会进行 instrumentation,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 已知是 set 接口的一部分,并将进行 instrumentation。

但是此类还不能完全工作:需要一些粘合代码来使其适应 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 和 iterate 接口即可。Append 和 remove 方法将在调用时将映射实体作为单个参数,而 iterator 方法在调用时没有参数,并且必须返回一个迭代器。

自定义基于字典的集合

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__() 版本应使用 collection.internally_instrumented() 进行装饰,如果它们调用 KeyFuncDict 上的相同方法。这是因为 KeyFuncDict 上的方法已经 instrumented - 从已经 instrumented 的调用中调用它们可能会导致事件被重复或不适当地触发,从而在极少数情况下导致内部状态损坏

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 或在鸭子类型类中提供类字典集合行为,则会自动 instrument 所有“类字典”方法。但是,您必须装饰 appender 和 remover 方法 - SQLAlchemy 默认情况下没有基本字典接口中的兼容方法可供使用。迭代将通过 values() 进行,除非另有装饰。

Instrumentation 和自定义类型

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

装饰器是轻量级的,并且在关系之外是 no-op,但是当在其他地方触发时,它们确实会增加不必要的开销。当使用库类作为集合时,使用“trivial subclass”技巧来限制装饰器仅用于您在关系中的用法可能是一种好的做法。例如

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


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

ORM 对内置类型使用这种方法,当直接使用 listsetdict 时,会静默地替换为 trivial subclass。

集合 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]]

一种基于字典的集合类型,具有基于属性的键控。

在版本 2.0 中更改:attribute_mapped_collection 重命名为 attribute_keyed_dict()

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

注意

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

另请参阅

字典集合 - 使用背景

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

  • ignore_unpopulated_attribute

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

    在版本 2.0 中新增:如果用于字典键的属性被确定从未填充任何值,则默认情况下会引发错误。attribute_keyed_dict.ignore_unpopulated_attribute 参数可以设置,这将指示应忽略此条件,并静默跳过 append 操作。这与 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]]

一种基于字典的集合类型,具有基于列的键控。

在版本 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 参数可以设置,这将指示应忽略此条件,并静默跳过 append 操作。这与 1.x 系列的行为相反,后者会错误地用任意键值 None 填充字典中的值。

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

一种基于字典的集合类型,具有任意键控。

Changed in version 2.0: 重命名 mapped_collectionkeyfunc_mapping()

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

注意

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

另请参阅

字典集合 - 使用背景

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

  • ignore_unpopulated_attribute

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

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

sqlalchemy.orm.attribute_mapped_collection = <function attribute_keyed_dict>

一种基于字典的集合类型,具有基于属性的键控。

在版本 2.0 中更改:attribute_mapped_collection 重命名为 attribute_keyed_dict()

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

注意

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

另请参阅

字典集合 - 使用背景

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

  • ignore_unpopulated_attribute

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

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

sqlalchemy.orm.column_mapped_collection = <function column_keyed_dict>

一种基于字典的集合类型,具有基于列的键控。

在版本 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 参数可以设置,这将指示应忽略此条件,并静默跳过 append 操作。这与 1.x 系列的行为相反,后者会错误地用任意键值 None 填充字典中的值。

sqlalchemy.orm.mapped_collection = <function keyfunc_mapping>

一种基于字典的集合类型,具有任意键控。

Changed in version 2.0: 重命名 mapped_collectionkeyfunc_mapping()

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

注意

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

另请参阅

字典集合 - 使用背景

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

  • ignore_unpopulated_attribute

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

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

class sqlalchemy.orm.KeyFuncDict

ORM 映射字典类的基类。

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

Changed in version 2.0: 重命名 MappedCollectionKeyFuncDict

类签名

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) 应返回相同的输出。基于可变属性的键控可能会导致无法访问的实例“丢失”在集合中。

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

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

method sqlalchemy.orm.KeyFuncDict.popitem()

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

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

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

通过值移除一个项,并咨询 keyfunc 获取键。

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

通过值添加一个项,并咨询 keyfunc 获取键。

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

如果键不在字典中,则插入键,其值为 default。

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

method 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 映射字典类的基类。

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

Changed in version 2.0: 重命名 MappedCollectionKeyFuncDict

集合内部机制

对象名称 描述

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

加载一个新的集合,根据先前的相似成员关系触发事件。

collection

实体集合类的装饰器。

collection_adapter

attrgetter(attr, …) –> attrgetter 对象

CollectionAdapter

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

InstrumentedDict

内置字典的工具化版本。

InstrumentedList

内置列表的工具化版本。

InstrumentedSet

内置集合的工具化版本。

prepare_instrumentation(factory)

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

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

加载一个新的集合,根据先前的相似成员关系触发事件。

values 中的实例追加到 new_adapter 上。将为任何不存在于 existing_adapter 中的实例触发事件。任何在 existing_adapter 中但不存在于 values 中的实例都将触发移除事件。

参数:
class 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): ...
method sqlalchemy.orm.collections.collection.static 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)

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

Deprecated since version 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’,并确保其传播到集合事件。

方法 sqlalchemy.orm.collections.collection.静态方法 removes(arg)

将此方法标记为从集合中移除实体。

为该方法添加“从集合中移除”的处理逻辑。装饰器参数指示哪个方法参数持有要移除的 SQLAlchemy 相关值。参数可以通过位置(即整数)或名称指定。

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

对于在调用时值未知的方法,请使用 collection.removes_return。

方法 sqlalchemy.orm.collections.collection.静态方法 removes_return()

将此方法标记为从集合中移除实体。

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

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

对于在调用时值已知的方法,请使用 collection.remove。

方法 sqlalchemy.orm.collections.collection.静态方法 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)。

sqlalchemy.orm.collections.CollectionAdapter

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

将基本级别的集合操作(append、remove、iterate)代理到下层的 Python 集合,并为实体进入或离开集合时发出添加/移除事件。

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

sqlalchemy.orm.collections.InstrumentedDict

内置字典的工具化版本。

类签名

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

sqlalchemy.orm.collections.InstrumentedList

内置列表的工具化版本。

类签名

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

sqlalchemy.orm.collections.InstrumentedSet

内置集合的工具化版本。

类签名

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

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

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

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

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