相同对象的缓慢相等评估 (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.array
和 pandas.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 的渴望。
有什么原因 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.array
和 pandas.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 的渴望。