SqlAlchemy 2.0 中文文档(七十三)(2)https://developer.aliyun.com/article/1562208
改进多对一查询表达式的行为
当构建一个将多对一关系与对象值进行比较的查询时,例如:
u1 = session.query(User).get(5) query = session.query(Address).filter(Address.user == u1)
上述表达式Address.user == u1
,最终编译成一个基于User
对象的主键列的 SQL 表达式,如"address.user_id = 5"
,使用延迟可调用以在绑定表达式中尽可能晚地检索值5
。这是为了适应这样一个用例,即Address.user == u1
表达式可能针对尚未刷新的User
对象,该对象依赖于服务器生成的主键值,以及该表达式始终返回正确的结果,即使自创建表达式以来u1
的主键值已更改。
然而,这种行为的一个副作用是,如果在评估表达式时u1
最终过期,将导致额外的 SELECT 语句,并且在u1
也从Session
中分离的情况下,将引发错误:
u1 = session.query(User).get(5) query = session.query(Address).filter(Address.user == u1) session.expire(u1) session.expunge(u1) query.all() # <-- would raise DetachedInstanceError
当 Session
提交并且 u1
实例超出范围时,对象的过期 / 删除可能会隐式发生,因为 Address.user == u1
表达式不会强烈引用对象本身,而只会引用其InstanceState
。
修复方法是允许 Address.user == u1
表达式根据在表达式编译时尝试正常检索或加载值的基础上评估值 5
,就像现在一样,但如果对象是分离的并且已过期,则从 InstanceState
上的新机制中检索,该机制将在该状态上的当属性过期时为该特定属性的最后已知值进行存储。仅当表达式功能需要时,此机制才会为特定属性 / InstanceState
启用以节省性能 / 内存开销。
最初,尝试了诸如立即评估表达式并在以后尝试加载值时采取各种安排的简单方法,但困难的边缘案例是正在更改的列属性的值(通常是自然主键)的值。为了确保像 Address.user == u1
这样的表达式始终返回 u1
的当前状态的正确答案,如果需要,它将返回持久对象的当前数据库持久值,通过 SELECT 查询取消到期,并且对于分离的对象,它将返回最近已知的值,无论对象何时被使用新特性将其过期在InstanceState
中跟踪列属性的最后已知值时。
当值无法评估时,现代属性 API 功能用于指示特定的错误消息,两种情况是当列属性从未设置过时,以及当对象在首次评估时已经过期且现在分离时。在所有情况下,DetachedInstanceError
不再被引发。
多对一替换不会对“raiseload”或“old”对象进行提升
考虑到延迟加载将在多对一关系上进行以加载“old”值的情况,如果关系未指定relationship.active_history
标志,则不会为分离的对象引发断言:
a1 = session.query(Address).filter_by(id=5).one() session.expunge(a1) a1.user = some_user
上面,当在分离的 a1
对象上替换 .user
属性时,会引发 DetachedInstanceError
,因为属性试图从标识映射中检索 .user
的先前值。更改是现在操作会在不加载旧值的情况下继续进行。
对 lazy="raise"
加载器策略也进行了相同的更改:
class Address(Base): # ... user = relationship("User", ..., lazy="raise")
以前,a1.user
的关联会引发 “raiseload” 异常,因为属性试图检索先前的值。现在在加载 “旧” 值的情况下跳过了此断言。
为 ORM 属性实现了 “del”
Python del
操作实际上不能用于映射属性,无论是标量列还是对象引用。已经添加了对此的支持,使其能够正常工作,其中 del
操作大致相当于将属性设置为 None
值:
some_object = session.query(SomeObject).get(5) del some_object.some_attribute # from a SQL perspective, works like "= None"
添加到 InstanceState 的 info 字典
将 .info
字典添加到 InstanceState
类,该类是通过在映射对象上调用 inspect()
获得的。这允许自定义方案为对象添加关于对象的其他信息,该信息将随对象在内存中的完整生命周期一起传递:
from sqlalchemy import inspect u1 = User(id=7, name="ed") inspect(u1).info["user_info"] = "7|ed"
水平分片扩展支持批量更新和删除方法
ShardedQuery
扩展对象支持 Query.update()
和 Query.delete()
批量更新/删除方法。在调用它们时会咨询 query_chooser
可调用对象,以便根据给定的条件在多个分片上运行更新/删除操作。
协会代理改进
虽然没有特定的原因,但本次周期内协会代理扩展进行了许多改进。
协会代理有新的 cascade_scalar_deletes 标志
给定一个映射如下:
class A(Base): __tablename__ = "test_a" id = Column(Integer, primary_key=True) ab = relationship("AB", backref="a", uselist=False) b = association_proxy( "ab", "b", creator=lambda b: AB(b=b), cascade_scalar_deletes=True ) class B(Base): __tablename__ = "test_b" id = Column(Integer, primary_key=True) ab = relationship("AB", backref="b", cascade="all, delete-orphan") class AB(Base): __tablename__ = "test_ab" a_id = Column(Integer, ForeignKey(A.id), primary_key=True) b_id = Column(Integer, ForeignKey(B.id), primary_key=True)
对 A.b
的赋值将生成一个 AB
对象:
a.b = B()
A.b
关联是标量的,并包括一个新标志 AssociationProxy.cascade_scalar_deletes
。设置了该标志后,将 A.b
设置为 None
将同时移除 A.ab
。默认行为仍然是保留 a.ab
不变:
a.b = None assert a.ab is None
虽然最初看起来这个逻辑应该只需查看现有关系的“cascade”属性,但仅凭这一点并不清楚代理对象是否应该被移除,因此行为被作为显式选项提供。
另外,del
现在对标量的工作方式类似于设置为 None
:
del a.b assert a.ab is None
#4308 #### AssociationProxy 在每个类的基础上存储类特定的状态
AssociationProxy
对象根据它关联的父映射类做出许多决策。虽然 AssociationProxy
在历史上始作为相对简单的‘getter’,但很早就显而易见它还需要做出关于它所引用的属性类型的决策——例如标量或集合、映射对象或简单值等。为了实现这一点,它需要检查映射属性或其他引用描述符或属性,从其父类中引用。然而,在 Python 描述符机制中,描述符仅在上下文中被访问时才了解其“父”类,例如调用 MyClass.some_descriptor
,这将调用 __get__()
方法,该方法传入类。因此,AssociationProxy
对象将存储特定于该类的状态,但只有在首次调用此方法时才会; 在首次访问 AssociationProxy
作为描述符之前尝试检查此状态会引发错误。此外,它将假定__get__()
看到的第一个类是它需要知道的唯一父类。尽管如果一个特定类有继承的子类,协会代理实际上是代表超过一个父类工作的,即使它没有明确地被重新使用。即使有这个缺陷,协会代理仍然可以通过其当前行为取得很大进展,但在某些情况下仍存在缺陷,以及确定最佳“所有者”类的复杂问题。
这些问题现在得到解决,因为当调用 __get__()
时,AssociationProxy
不再修改自己的内部状态;相反,每个类都生成一个名为 AssociationProxyInstance
的新对象,处理特定于特定映射父类的所有状态(当父类未映射时,不会生成 AssociationProxyInstance
)。关联代理的单一“拥有类”概念,尽管在 1.1 中得到改进,但实质上已被一种方法取代,即 AP 现在可以平等对待任意数量的“拥有”类。
为了适应希望检查此状态的应用程序,而不一定调用 __get__()
的 AssociationProxy
,添加了一个新方法 AssociationProxy.for_class()
,提供对特定类的 AssociationProxyInstance
的直接访问,示例如下:
class User(Base): # ... keywords = association_proxy("kws", "keyword") proxy_state = inspect(User).all_orm_descriptors["keywords"].for_class(User)
一旦我们有了 AssociationProxyInstance
对象,在上面的示例中存储在 proxy_state
变量中,我们可以查看特定于 User.keywords
代理的��性,比如 target_class
:
>>> proxy_state.target_class Keyword
#3423 #### AssociationProxy 现在为基于列的目标提供标准列运算符
给定一个 AssociationProxy
,其中目标是数据库列,而不是对象引用或另一个关联代理:
class User(Base): # ... elements = relationship("Element") # column-based association proxy values = association_proxy("elements", "value") class Element(Base): # ... value = Column(String)
User.values
关联代理指向 Element.value
列。现在可以进行标准列操作,比如 like
:
>>> print(s.query(User).filter(User.values.like("%foo%"))) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND element.value LIKE :value_1)
equals
:
>>> print(s.query(User).filter(User.values == "foo")) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND element.value = :value_1)
当与 None
进行比较时,IS NULL
表达式会增加一个测试,即相关行根本不存在;这与以前的行为相同:
>>> print(s.query(User).filter(User.values == None)) SELECT "user".id AS user_id FROM "user" WHERE (EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND element.value IS NULL)) OR NOT (EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id))
请注意,ColumnOperators.contains()
操作实际上是一个字符串比较操作符;这是行为上的变化,以前,关联代理仅使用.contains
作为列表包含操作符。通过基于列的比较,它现在的行为类似于“like”:
>>> print(s.query(User).filter(User.values.contains("foo"))) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND (element.value LIKE '%' || :value_1 || '%'))
为了测试User.values
集合是否包含值"foo"
,应该使用等于操作符(例如User.values == 'foo'
);这在以前的版本中也适用。
当使用基于对象的关联代理与集合时,行为与以前相同,即测试集合成员资格,例如给定一个映射:
class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True) user_elements = relationship("UserElement") # object-based association proxy elements = association_proxy("user_elements", "element") class UserElement(Base): __tablename__ = "user_element" id = Column(Integer, primary_key=True) user_id = Column(ForeignKey("user.id")) element_id = Column(ForeignKey("element.id")) element = relationship("Element") class Element(Base): __tablename__ = "element" id = Column(Integer, primary_key=True) value = Column(String)
.contains()
方法产生与以前相同的表达式,测试User.elements
列表中是否存在Element
对象:
>>> print(s.query(User).filter(User.elements.contains(Element(id=1)))) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM user_element WHERE "user".id = user_element.user_id AND :param_1 = user_element.element_id)
总的来说,这种变化是基于 AssociationProxy stores class-specific state on a per-class basis 的架构变化实现的;因为代理现在在生成表达式时会产生额外的状态,所以AssociationProxyInstance
类现在有对象目标和列目标版本。
关联代理现在强引用父对象
关联代理集合长期以来只维护对父对象的弱引用的行为被还原;代理现在将在代理集合本身也在内存中的情况下维护对父对象的强引用,消除了“过时的关联代理”错误。这种变化是基于实验性的基础进行的,以查看是否会出现任何导致副作用的用例。
举例来说,给定一个带有关联代理的映射:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) bs = relationship("B") b_data = association_proxy("bs", "data") class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id")) data = Column(String) a1 = A(bs=[B(data="b1"), B(data="b2")]) b_data = a1.b_data
以前,如果a1
在作用域外被删除:
del a1
在a1
从作用域中删除后尝试迭代b_data
集合会引发错误“过时的关联代理,父对象已经超出作用域”。这是因为关联代理需要访问实际的a1.bs
集合以产生视图,在此更改之前,它只维护对a1
的弱引用。特别是,用户在执行内联操作时经常会遇到此错误,例如:
collection = session.query(A).filter_by(id=1).first().b_data
由于A
对象在b_data
集合实际使用之前可能被垃圾回收。
变化在于b_data
集合现在维护对a1
对象的强引用,使其保持存在:
assert b_data == ["b1", "b2"]
这个改变带来了一个副作用,即如果一个应用程序像上面那样传递集合,父对象在集合被丢弃之前不会被垃圾回收。一如既往,如果a1
在特定的Session
中是持久的,它将一直保留在该会话的状态中,直到被垃圾回收。
请注意,如果这个改变导致问题,可能会进行修订。
为集合、字典实现了批量替换与 AssociationProxy
将集合或字典分配给关联代理集合现在应该能够正确工作,而以前会为现有键重新创建关联代理成员,导致由于相同对象的删除+插入而导致潜在的刷新失败问题,现在应该只在适当的情况下创建新的关联对象:
class A(Base): __tablename__ = "test_a" id = Column(Integer, primary_key=True) b_rel = relationship( "B", collection_class=set, cascade="all, delete-orphan", ) b = association_proxy("b_rel", "value", creator=lambda x: B(value=x)) class B(Base): __tablename__ = "test_b" __table_args__ = (UniqueConstraint("a_id", "value"),) id = Column(Integer, primary_key=True) a_id = Column(Integer, ForeignKey("test_a.id"), nullable=False) value = Column(String) # ... s = Session(e) a = A(b={"x", "y", "z"}) s.add(a) s.commit() # re-assign where one B should be deleted, one B added, two # B's maintained a.b = {"x", "z", "q"} # only 'q' was added, so only one new B object. previously # all three would have been re-created leading to flush conflicts # against the deleted ones. assert len(s.new) == 1
#2642 #### 关联代理新增了新的 cascade_scalar_deletes 标志
给定一个映射如下:
class A(Base): __tablename__ = "test_a" id = Column(Integer, primary_key=True) ab = relationship("AB", backref="a", uselist=False) b = association_proxy( "ab", "b", creator=lambda b: AB(b=b), cascade_scalar_deletes=True ) class B(Base): __tablename__ = "test_b" id = Column(Integer, primary_key=True) ab = relationship("AB", backref="b", cascade="all, delete-orphan") class AB(Base): __tablename__ = "test_ab" a_id = Column(Integer, ForeignKey(A.id), primary_key=True) b_id = Column(Integer, ForeignKey(B.id), primary_key=True)
对A.b
的赋值将生成一个AB
对象:
a.b = B()
A.b
关联是标量的,并包括一个新标志AssociationProxy.cascade_scalar_deletes
。当设置时,将A.b
设置为None
将同时移除A.ab
。默认行为仍然是保留a.ab
在原地:
a.b = None assert a.ab is None
起初似乎很直观的是,这个逻辑应该只查看现有关系的“级联”属性,但仅仅从那个属性本身并不清楚代理对象是否应该被移除,因此行为被作为一个明确的选项提供。
另外,del
现在对标量的操作方式与设置为None
类似:
del a.b assert a.ab is None
关联代理在每个类上存储特定于类的状态
AssociationProxy
对象基于其关联的父映射类做出许多决策。虽然 AssociationProxy
在历史上最初是一个相对简单的“getter”,但很快就明显地需要做出关于其引用的属性类型的决策——比如标量或集合、映射对象或简单值等。为了实现这一点,它需要检查映射属性或其他引用描述符或属性,这些都是从其父类引用的。然而,在 Python 描述符机制中,描述符只有在在其“父”类的上下文中被访问时才会了解其“父”类,比如调用 MyClass.some_descriptor
,这会调用 __get__()
方法并传递类。因此,AssociationProxy
对象将存储特定于该类的状态,但只有在调用此方法后才会这样;在未首先将 AssociationProxy
作为描述符访问的情况下尝试检查此状态将引发错误。此外,它会假定 __get__()
看到的第一个类就是它需要了解的唯一父类。尽管如果一个特定类有继承的子类,关联代理实际上是代表不止一个父类工作,即使没有明确重用。尽管即使有这个缺点,关联代理仍然可以通过其当前行为取得相当大的进展,但在某些情况下仍存在缺陷,以及确定最佳“所有者”类的复杂问题。
现在这些问题已经解决了,因为在调用 __get__()
时,AssociationProxy
不再修改自己的内部状态;相反,针对每个类生成一个新对象,称为 AssociationProxyInstance
,该对象处理与特定映射的父类相关的所有状态(当父类未映射时,不会生成 AssociationProxyInstance
)。一个关联代理的单一“拥有类”的概念,尽管在 1.1 中得到了改进,但基本上已被一种方法取代,即 AP 现在可以平等对待任意数量的“拥有”类。
为了适应那些想要检查此状态的应用,而不一定调用 __get__()
的应用程序,添加了一个新方法 AssociationProxy.for_class()
,它提供了对特定类的 AssociationProxyInstance
的直接访问,如下所示:
class User(Base): # ... keywords = association_proxy("kws", "keyword") proxy_state = inspect(User).all_orm_descriptors["keywords"].for_class(User)
一旦我们拥有 AssociationProxyInstance
对象,在上面的示例中存储在 proxy_state
变量中,我们可以查看特定于 User.keywords
代理的属性,例如 target_class
:
>>> proxy_state.target_class Keyword
关联代理现在为基于列的目标提供标准列操作符
给定一个 AssociationProxy
,其中目标是数据库列,并且不是对象引用或另一个关联代理:
class User(Base): # ... elements = relationship("Element") # column-based association proxy values = association_proxy("elements", "value") class Element(Base): # ... value = Column(String)
User.values
关联代理指的是 Element.value
列。现在可使用标准列操作,例如 like
:
>>> print(s.query(User).filter(User.values.like("%foo%"))) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND element.value LIKE :value_1)
equals
:
>>> print(s.query(User).filter(User.values == "foo")) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND element.value = :value_1)
当与 None
比较时,IS NULL
表达式会增加一个测试,即相关行根本不存在;这与以前的行为相同:
>>> print(s.query(User).filter(User.values == None)) SELECT "user".id AS user_id FROM "user" WHERE (EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND element.value IS NULL)) OR NOT (EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id))
请注意,ColumnOperators.contains()
操作符实际上是一个字符串比较操作符;这是一个行为上的改变,之前,关联代理仅将 .contains
用作列表包含操作符。通过基于列的比较,它现在的行为类似于“like”:
>>> print(s.query(User).filter(User.values.contains("foo"))) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM element WHERE "user".id = element.user_id AND (element.value LIKE '%' || :value_1 || '%'))
为了测试 User.values
集合是否包含值 "foo"
,应该使用等于操作符(例如 User.values == 'foo'
);这在之前的版本中也适用。
当使用基于对象的关联代理与集合时,行为与以前相同,即测试集合成员资格,例如给定一个映射:
class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True) user_elements = relationship("UserElement") # object-based association proxy elements = association_proxy("user_elements", "element") class UserElement(Base): __tablename__ = "user_element" id = Column(Integer, primary_key=True) user_id = Column(ForeignKey("user.id")) element_id = Column(ForeignKey("element.id")) element = relationship("Element") class Element(Base): __tablename__ = "element" id = Column(Integer, primary_key=True) value = Column(String)
.contains()
方法产生与之前相同的表达式,测试 User.elements
列表中是否存在 Element
对象:
>>> print(s.query(User).filter(User.elements.contains(Element(id=1)))) SELECT "user".id AS user_id FROM "user" WHERE EXISTS (SELECT 1 FROM user_element WHERE "user".id = user_element.user_id AND :param_1 = user_element.element_id)
总的来说,这个改变是基于关联代理在每个类的基础上存储特定于类的状态的架构变化而启用的;由于代理现在在生成表达式时会产生额外的状态,AssociationProxyInstance
类现在有对象目标和列目标版本。
关联代理现在强引用父对象
关联代理集合长期维持对父对象的弱引用的行为被撤销;代理现在将在代理集合本身也在内存中的情况下维持对父对象的强引用,消除了“过时的关联代理”错误。这个改变是基于试验性基础进行的,以查看是否会出现任何导致副作用的用例。
举例来说,给定一个带有关联代理的映射:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) bs = relationship("B") b_data = association_proxy("bs", "data") class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id")) data = Column(String) a1 = A(bs=[B(data="b1"), B(data="b2")]) b_data = a1.b_data
以前,如果 a1
超出范围被删除:
del a1
在 a1
从范围中删除后尝试迭代 b_data
集合会引发错误 "过时的关联代理,父对象已超出范围"
。这是因为关联代理需要访问实际的 a1.bs
集合以生成视图,在此改变之前,它只维持对 a1
的弱引用。特别是,用户在执行内联操作时经常会遇到这个错误:
collection = session.query(A).filter_by(id=1).first().b_data
上面的情况是因为 A
对象在 b_data
集合实际使用之前会被垃圾回收。
变化在于 b_data
集合现在维持对 a1
对象的强引用,使其保持存在:
assert b_data == ["b1", "b2"]
这种改变引入了一个副作用,即如果应用程序像上面那样传递集合,父对象在集合被丢弃之前不会被垃圾回收。一如既往,如果a1
在特定的Session
内是持久的,它将一直保留在该会话的状态中,直到被垃圾回收。
注意,如果这种改变导致问题,可能会进行修订。
使用 AssociationProxy 为集合实现批量替换的功能
现在,将集合分配给关联代理集合应该可以正常工作,而以前会为现有键重新创建关联代理成员,导致由于删除+插入相同对象而导致潜在刷新失败的问题,现在应该只在适当的情况下创建新的关联对象:
class A(Base): __tablename__ = "test_a" id = Column(Integer, primary_key=True) b_rel = relationship( "B", collection_class=set, cascade="all, delete-orphan", ) b = association_proxy("b_rel", "value", creator=lambda x: B(value=x)) class B(Base): __tablename__ = "test_b" __table_args__ = (UniqueConstraint("a_id", "value"),) id = Column(Integer, primary_key=True) a_id = Column(Integer, ForeignKey("test_a.id"), nullable=False) value = Column(String) # ... s = Session(e) a = A(b={"x", "y", "z"}) s.add(a) s.commit() # re-assign where one B should be deleted, one B added, two # B's maintained a.b = {"x", "z", "q"} # only 'q' was added, so only one new B object. previously # all three would have been re-created leading to flush conflicts # against the deleted ones. assert len(s.new) == 1
多对一反向引用在移除操作期间检查集合中的重复项
当 ORM 映射的集合作为 Python 序列存在时,通常是 Python list
,这是relationship()
的默认值,包含重复项,并且对象从一个位置被移除但未从其他位置移除时,一个多对一的反向引用会将其属性设置为None
,即使一对多方仍然表示对象存在。尽管一对多集合在关系模型中不能有重复项,但使用序列集合的 ORM 映射的relationship()
在内存中可以有重复项,但这些重复状态既不能持久化也不能从数据库中检索。特别是,在列表中临时存在重复项是 Python“交换”操作的固有特性。考虑一个标准的一对多/多对一设置:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) bs = relationship("B", backref="a") class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id"))
如果我们有一个具有两个B
成员的A
对象,并执行交换:
a1 = A(bs=[B(), B()]) a1.bs[0], a1.bs[1] = a1.bs[1], a1.bs[0]
在上述操作期间,拦截标准 Python __setitem__
__delitem__
方法会提供一个中间状态,其中第二个B()
对象在集合中出现两次。当B()
对象从一个位置移除时,B.a
反向引用会将引用设置为None
,导致在刷新期间删除A
和B
对象之间的链接。相同的问题也可以使用普通重复项来演示:
>>> a1 = A() >>> b1 = B() >>> a1.bs.append(b1) >>> a1.bs.append(b1) # append the same b1 object twice >>> del a1.bs[1] >>> a1.bs # collection is unaffected so far... [<__main__.B object at 0x7f047af5fb70>] >>> b1.a # however b1.a is None >>> >>> session.add(a1) >>> session.commit() # so upon flush + expire.... >>> a1.bs # the value is gone []
修复确保在反向引用触发之前,在集合被改变之前,检查集合中是否恰好有一个或零个目标项的实例,然后在取消多对一方面时使用线性搜索,目前使用list.search
和list.__contains__
。
最初人们认为在集合内部需要使用基于事件的引用计数方案,以便在整个集合的生命周期内跟踪所有重复的实例,这将对所有集合操作产生性能/内存/复杂性影响,包括非常频繁的加载和追加操作。取而代之的方法是将额外的开销限制在较不常见的集合移除和批量替换操作上,并且观察到的线性扫描开销可以忽略不计;关系绑定集合的线性扫描已经在工作单元中使用,以及在集合被批量替换时已经被使用。
SqlAlchemy 2.0 中文文档(七十三)(4)https://developer.aliyun.com/article/1562226