【问题标题】:What happens when you call `if key in dict`当你调用`if key in dict`时会发生什么
【发布时间】:2012-10-21 20:32:03
【问题描述】:

我有一个实现__hash____eq__ 的类(我们称之为myClass)。我还有一个 dictmyClass 对象映射到某个值,计算需要一些时间。

在我的程序过程中,许多(数百万)myClass 对象被实例化。这就是我使用dict 来跟踪这些值的原因。

但是,有时新的myClass 对象可能等同于旧的对象(由__eq__ 方法定义)。因此,与其再次计算该对象的值,我宁愿在dict 中查找旧的myClass 对象的值。为此,我使用if myNewMyClassObj in dict

这是我的问题:

当我使用 in 子句时,会调用什么 __hash____eq__?使用dict 的意义在于它的查找时间为 O(1)。所以必须调用__hash__。但是如果__hash____eq__ 不是等效方法怎么办?在这种情况下,if myNewMyClassObj in dict 会误报吗?

跟进问题:

我想尽量减少dict 中的条目数,因此我希望在dict 中只保留一组等效的myClass 对象中的一个。同样,在计算 if myNewClassObj in dict 时似乎需要调用 __eq__,这会将 dict 的 O(1) 查找时间污染为 O(n) 查找时间

【问题讨论】:

    标签: python hash dictionary equality


    【解决方案1】:

    首先,__hash__(myNewMyClassObj) 被调用。如果在字典中没有找到具有相同哈希值的对象,Python 会假定 myNewMyClassObj 不在字典中。 (请注意,Python 要求每当 __eq__ 对两个对象的计算结果相等时,它们的 __hash__ 必须相同。)

    如果在字典中找到一些具有相同__hash__ 的对象,则在每个对象上调用__eq__。如果__eq__ 对它们中的任何一个计算为相等,则myNewMyClassObj in dict_ 返回True。

    因此,您只需要确保__eq____hash__ 都快。

    对于您的后续问题:是的,dict_ 仅存储一组等效的MyClass 对象(由__eq__ 定义)中的一个。 (按照设定。)

    请注意,__eq__ 仅在具有相同哈希值并分配到相同存储桶的对象上调用。此类对象的数量通常非常少(dict 实现确保了这一点)。所以你仍然有(大致)O(1) 查找性能。

    【讨论】:

      【解决方案2】:

      __hash__ 将始终被调用; __eq__ 如果对象确实在字典中,或者另一个具有相同哈希的对象在字典中,则将被调用。哈希值用于缩小可能键的选择范围。这些键按哈希值分组到“桶”中,但是对于查找,Python 仍然必须检查桶中的每个键是否与查找键相等。见http://wiki.python.org/moin/DictionaryKeys。看看这些例子:

      >>> class Foo(object):
      ...     def __init__(self, x):
      ...         self.x = x
      ...     
      ...     def __hash__(self):
      ...         print "Hash"
      ...         return hash(self.x)
      ... 
      ...     def __eq__(self, other):
      ...         print "Eq"
      ...         return self.x == other.x
      >>> Foo(1) in d
      Hash
      Eq
      10: True
      >>> Foo(2) in d
      Hash
      Eq
      11: True
      >>> Foo(3) in d
      Hash
      Eq
      12: True
      >>> Foo(4) in d
      Hash
      13: False
      

      在该示例中,您可以看到始终调用__hash__。当对象在字典中时,__eq__ 每次查找都会调用一次,因为它们都有不同的哈希值,因此一次相等性检查足以验证具有该哈希值的对象确实是被查询的对象。最后一种情况没有调用__eq__,因为dict中没有一个对象和Foo(4)有相同的hash值,所以Python不需要继续__eq__

      >>> class Foo(object):
      ...     def __init__(self, x):
      ...         self.x = x
      ...     
      ...     def __hash__(self):
      ...         print "Hash"
      ...         return 1
      ... 
      ...     def __eq__(self, other):
      ...         print "Eq"
      ...         return self.x == other.x
      >>> d = {Foo(1): 2, Foo(2): 3, Foo(3): 4}
      Hash
      Hash
      Eq
      Hash
      Eq
      Eq
      >>> Foo(1) in d
      Hash
      Eq
      18: True
      >>> Foo(2) in d
      Hash
      Eq
      Eq
      19: True
      >>> Foo(3) in d
      Hash
      Eq
      Eq
      Eq
      20: True
      >>> Foo(4) in d
      Hash
      Eq
      Eq
      Eq
      21: False
      

      在这个版本中,所有对象都具有相同的哈希值。在这种情况下,__eq__ 总是被调用,有时会被多次调用,因为哈希不区分值,所以 Python 需要显式检查字典中所有值的相等性,直到找到相等的值(或发现没有它们等于它正在寻找的那个)。有时它会在第一次尝试时找到它(上面的Foo(1) in dict),有时它必须检查所有值。

      【讨论】:

      • @MartijnPieters:我只是在包含它们之前不小心点击了保存,它们现在就在那里。
      • Python 在其哈希表中不使用存储桶:它使用插槽,每个插槽包含一个值。如果一个插槽已满,那么它将选择另一个插槽,依此类推,直到找到匹配或未使用的插槽。
      【解决方案3】:

      __hash__ 定义了对象放入的桶,__eq__ 只有在对象在同一个桶中时才会被调用。

      【讨论】:

        猜你喜欢
        • 2013-10-16
        • 2013-04-29
        • 2020-06-12
        • 2012-10-06
        • 2019-01-19
        • 1970-01-01
        • 2011-05-05
        • 1970-01-01
        • 2020-08-22
        相关资源
        最近更新 更多