比较SQLAlchemy对象实例的属性相等性

33

我的Flask-Restful应用有很多“对象”。在应用的第一个版本中,这些对象是简单的数据结构,没有行为,实现为字典或字典列表。

这些“对象”的属性可能会发生变化。我使用生成器函数来跟踪这些变化,然后通过服务器发送事件(SSE)通知Web客户端。 这是通过维护要跟踪的对象的“旧”副本,并将其与最新状态进行比较来实现的。

在应用的下一个版本中,我使用SQLAlchemy从SQLite DB中填充“对象”。 现在,这些对象是作为SQLAlchemy声明性类或此类类的列表实现的。

为了仅基于属性相等性比较“旧”和“新”实例,我必须添加__eq__覆盖到我的SQLAlchemy对象中。 也就是说,当属性具有相同的值时,实例被认为是相等/未更改的。(我已经在本问题底部发布了示例代码。)

技术上,这可以工作,但引发了一些架构警报:我是否朝着错误的方向前进?

a)如果我为SQAlchemy对象添加__eq____ne__覆盖,那么当我稍后想重新持久化对象到数据库时, 这会引起SQLAlchemy的问题吗?

b)SQLAlchemy对象应该扩展到我的应用程序的哪个程度:有一个“Pythonic最佳实践”吗?也就是说,将业务逻辑/与DB持久化无关的行为(例如跟踪更改)扩展到SQLAlchemy对象是否可以接受? 或者它们仅应作为在数据库和服务器之间传输数据的简单DTO,而业务逻辑则由其他对象处理?

注意:对于通过REST API和SSE公开给客户端的数据,应该将其从Web服务器和数据库的实现细节中抽象出来,因此不属于本问题的范围。

sqlalchemy id equality vs reference equality https://codereview.stackexchange.com/questions/93511/data-transfer-objects-vs-entities-in-java-rest-server-application http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model/

class EqualityMixin(object):
# extended from the concept in :
# https://dev59.com/H3RC5IYBdhLWcg3wKtz2

    def __eq__(self, other):
        classes_match = isinstance(other, self.__class__)
        a, b = deepcopy(self.__dict__), deepcopy(other.__dict__)
        #compare based on equality our attributes, ignoring SQLAlchemy internal stuff
        a.pop('_sa_instance_state', None)
        b.pop('_sa_instance_state', None)
        attrs_match = (a == b)
        return classes_match and attrs_match

    def __ne__(self, other):
        return not self.__eq__(other)

2
是的,你在这方面走错了方向。深度比较会非常缓慢且容易出错。请做好准备,并为希望包含此功能的每个对象编写显式的__eq __()例程,逐个比较您关心的实际属性。 - zzzeek
在上面的代码中,您可能希望对称地检查:classes_match = isinstance(other, self.class) and isinstance(self, other.class) - Daniel Böckenhoff
2个回答

7
我将深入探究Base类背后发生的事情,以展示__eq__和__ne__覆盖是正确的。当您通过调用declarative_base()来实例化您的Base类时,它在幕后使用元类进行设置(阅读此解释以更好地理解为什么涉及元类可能是值得的this metaclass explanation)。它执行一些可配置的设置,例如将自定义构造函数添加到您的Base类中,并设置如何从对象映射到表格。
然后,declarative_base()将返回DeclarativeMeta元类的新Base类实例。涉及元类的整个原因在于,在创建扩展Base的类时,它将映射到表格。如果您沿着这条路追踪一段时间,您将看到如何将您在对象上声明的列映射到表格。
self.cls.__mapper__ = mp_ = mapper_cls(
        self.cls, # cls is your model
        self.local_table,
        **self.mapper_args # the columns you have defined
    )

尽管实际的 Mapper 看起来变得非常复杂和低级,但在这个阶段它是使用主键和列而不是实际对象实例来操作。然而,这并不能确认它从未被使用,因此我查看了源代码中 == 和 != 的用法,并没有发现任何引起担忧的原因。
至于你的第二个问题,我只能提供自己的观点 - 我过去已经在这个主题上搜索过很多次,但没有找到标准的 SQL Alchemy 使用方法。我已经在几个项目中使用了 SQL Alchemy,感觉您对对象的使用可以尽可能地抽象出 session 生命周期。对我来说,当处理好会话时,足够的 Alchemy "magic" 已经从模型本身中抽象出来,因此当业务逻辑在类中进行时,它们与数据层已经足够远离,不会妨碍。

0
你可以在不同的会话之间比较实例;使用 session.merge 将实例 a 转换为新的会话。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接