相同对象的缓慢相等评估 (x == x)

Slow equality evaluation for identical objects (x == x)

有什么原因 x == x 没有被快速评估吗?我希望 __eq__ 会检查它的两个参数是否相同,如果是,则 return 立即为真。但它没有这样做:

s = set(range(100000000))
s == s # this doesn't short-circuit, so takes ~1 sec

对于内置插件,x == x 总是 return 我认为是正确的?对于用户定义的 类,我想有人可以定义不满足此 属性 的 __eq__,但是否有任何合理的用例?

我希望 x == x 得到快速评估的原因是当 memoizing functions with very large arguments:

时性能会受到巨大影响
from functools import lru_cache
@lru_cache()
def f(s):
    return sum(s)
large_obj = frozenset(range(50000000))
f(large_obj) # this takes >1 sec every time

注意@lru_cache之所以反复对于大对象慢并不是因为它需要计算__hash__(这只做了一次并且然后被@jsbueno硬缓存为pointed out),但是因为字典的散列table需要执行__eq__ 每次以确保它在桶中找到了正确的对象(哈希的相等性显然不够)。

更新:

看来这个问题应该分三种情况来考虑。

1) 用户定义类型(即非内置/标准库)。

正如@donkopotamus 所指出的,在某些情况下 x == x 不应评估为 True。例如,对于 numpy.arraypandas.Series 类型,结果有意不能转换为布尔值,因为不清楚自然语义应该是什么(False 意味着容器是空的,还是意味着所有项目在这是假的?)。

但是在这里,python 不需要做任何事情,因为如果合适的话,用户总是可以自己短路 x == x 比较:

def __eq__(self, other):
  if self is other:
    return True
  # continue normal evaluation

2) Python 内置/标准库类型。

a) 非容器。

据我所知,这种情况下可能已经实施了短路 - 我无法判断,因为无论哪种方式都非常快。

b) 容器(包括 str)。

正如@Karl Knechtel 评论的那样,如果在 self is not other 的情况下额外的开销超过了短路带来的节省,那么添加短路可能会损害整体性能。虽然理论上可行,但即使在那种情况下,相对而言开销也很小(容器比较永远不会超快)。当然,在短路有帮助的情况下,节省的费用会非常可观。

顺便说一句,事实证明 str 确实短路了:比较巨大的相同字符串是即时的。

正如您所说,有人可以很容易地定义一个您个人不赞成的 __eq__ ...例如,Institute of Electrical and Electronics Engineers 可能会愚蠢到这样做:

>>> float("NaN") == float("NaN")
False

另一个“不合理的用例”:

>>> bool(numpy.ma.masked == numpy.ma.masked)
False

甚至:

>>> numpy.arange(10) == numpy.arange(10)
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,  True], dtype=bool)

甚至连转换成 bool!

都不敢

因此 x == x 自动短路是肯定存在的。

偏离路线

不过下面的问题或许是个好问题:

Why doesn't set.__eq__ check for instance identity?

嗯,有人可能会想...因为集合 S 可能包含 NaN 并且由于 NaN 不能等于自身,所以这样的集合 S 肯定不能等于本身?调查中:

>>> s = set([float("NaN")])
>>> s == s
True

嗯,这很有趣,特别是因为:

>>> {float("NaN")} == {float("NaN")}
False

此行为是由于 Python 对 sequences to be reflexive 的渴望。