提问人:Feuermurmel 提问时间:4/9/2021 最后编辑:David ZFeuermurmel 更新时间:5/24/2023 访问量:268
__eq__() 多次调用,而不是在嵌套数据结构中调用一次
__eq__() called multiple times instead of once in nested data structure
问:
每年一两次,我会遇到以下问题:我有某种类型的比较操作可能很昂贵(例如,值很大以保存在内存中并且需要从磁盘加载,或者等式很难计算,因为单个值可能有很多表示,想想化学公式)。此类型是嵌套数据结构(例如嵌套列表或元组或某些树)的一部分。有时我注意到,对于单个比较的相同值,我的类型的比较运算符(等)被多次调用。__lt__
我将尝试通过以下示例来说明问题:
class X:
comparisons = 0
def __init__(self, value):
self.value = value
def __lt__(self, other):
return self.value < other.value
def __gt__(self, other):
return self.value > other.value
def __eq__(self, other):
X.comparisons += 1
return self.value == other.value
def nest_a_hundred_times(value):
for i in range(100): value = [value]
return value
print(nest_a_hundred_times(X(1)) < nest_a_hundred_times(X(0)))
print(X.comparisons)
在此示例中,我的类型具有昂贵的比较操作,我只是计算调用的次数,但其他操作也可能很昂贵。该类型的两个不相等值被创建并嵌套在单元素列表中很多次。X
__eq__()
运行示例将打印 、 。所以被叫了100次。False
100
__eq__()
我知道为什么会发生这种情况:列表对象的内置比较函数首先比较单个列表元素的相等性,以找出两个列表
在哪个索引处不同,然后再比较这些元素进行排序。我认为,当仅使用六个比较运算符(、、、、)作为定义排序的类型之间的接口时,实际上不可能避免这个问题。作为替代方法的一个例子,Haskell 有一个 Ord
类,它定义了一个函数来比较两个值。这允许通过在每个节点上仅调用一次来比较嵌套数据结构。==
!=
<
<=
>
>=
ordering
ordering
我的问题是:如何在 Python 中避免这个问题?与我的信念相反,是否有可能单独使用 Python 定义的比较运算符来避免这个问题?(我试图避免某种结果缓存,因为这不是性能问题,而是算法问题)。或者我是否需要构建自己的数据结构(列表、元组)并在其上实现函数?ordering
答:
从你提出问题的方式来看,我假设:
- 如果可能,您不希望覆盖。(我也不想,这是一个非常危险的想法)。
list.__eq__
- 如果需要,您可以覆盖 dunder () 方法。(据我所知,这是需要的)。
__
X
正如你所暗示的,因为你正在尝试解决在内置类型上实现内置操作的问题,所以我认为任何解决方案都不会特别干净(但嘿,也许其他答案会让我感到惊讶)。
我发现的一件有趣的事情是,如果你覆盖返回,它只会被调用一次。X.__eq__
True
class X:
...
def __eq__(self, other):
X.comparisons += 1
return True
现在显然这可能会产生一些问题,因为它会使 .但是,它会使和工作并且效率更高,所以如果你是积极的,你永远不需要使用 ==,我认为这可能是一种方式。X(1)==X(0)
nest_a_hundred_times(X(1)) == nest_a_hundred_times(X(0))
<
>
除此之外,我能想到的只是一个公认的混乱的黑客攻击,试图检测是被“>”还是由......__eq__
<
==
import inspect
class X:
...
def __eq__(self, other):
X.comparisons += 1
f = inspect.currentframe().f_back
fi = inspect.getframeinfo(f)
line_called_from = fi[-2][0]
called_from_lt = ('<' in line_called_from or '>' in line_called_from) and '==' not in line_called_from and 'eq(' not in line_called_from
if called_from_lt:
return True
return self.value == other.value
Your best option given the way list comparisons work might be to either:
a) If possible, when first needed, summarize and cache each value with a unique hash (e.g., a sortable string) that can be compared as a proxy for the heavyweight data you would otherwise compare; or
b) maintain a cache/memoization of recent comparison results based on object identities, and if a hit is found, just return the same result.
评论
list.__lt__
==
<
__eq__