散列生成器表达式

时间:2016-07-03 20:49:30

标签: python hash generator

显然python允许我散列像(i for i in [1, 2, 3, 4, 5])

这样的生成器表达式
>>> hash(i for i in [1, 2, 3, 4, 5])
8735741846615

然而,仔细观察,无论我投入哪个生成器,这个哈希值总是相同的!

>>> hash(i for i in range(2))
8735741846615
>>> hash(i for i in [1, 2, 3, 4, 5, 6])
8735741846615
>>> hash(i for i in [0, 1, 2, 3, 4, 5, 6])
8735741846615

为什么会这样?为什么甚至允许对发生器进行散列?

我需要这样做因为我将东西存储在字典缓存中。您可以通过传递对象列表来访问缓存中的条目。但是,具有不同项目的某些列表组仍应指向相同的条目,因为此处唯一重要的是每个列表项中一个属性的整数值。因此,它们应仅基于这些整数值进行散列,而不是与项本身相关的任何内容,以避免不必要的缓存未命中。

我知道你总是可以将表达式转换为元组,但我问你是否可以通过简单地使用没有元组容器的生成器的输出来绕过它,类似于sum()的方式可以用于这样的事情。

4 个答案:

答案 0 :(得分:5)

所以实际上有两个问题:

  1. 为什么生成器有哈希值,例如列表不?
  2. 为什么你总是得到相同的哈希?
  3. 对于2,答案很简单:在这种情况下,哈希基于对象的id。由于您实际上并未存储该对象,因此其内存将被重用。这意味着下一个生成器具有相同的id,因此哈希。

    对于1,答案是“因为他们可以”。 hash主要用于dictset以及允许识别对象的其他情况。这些情况设置了a == b也暗示hash(a) == hash(b)的约束(反之则不受约束)。

    现在,对于listdict和其他集合,相等性基于内容。例如,[1,2,3] == [1,2,3]无论两者是否都是相同的对象。这意味着如果向他们添加了某些内容,他们的平等会发生变化,因此他们的hash也会发生变化。因此,hash未定义,因为它必须是dict等工作的常量。

    相比之下,发电机可以有任何内容。考虑例如提供随机值的生成器。因此,按内容比较生成器是没有意义的。他们只是通过身份进行比较。因此,对于生成器,a == b等于id(a) == id(b)。反过来,这意味着hash(a)基于id(a)将始终通过hash上的相等来满足约束。

答案 1 :(得分:4)

哈希值可能基于对象标识,而不是其内容。你可能会看到这个结果,因为你没有存储发生器,因此它们被垃圾收集并重新使用它们的ID。以下是一些其他示例,表明它不仅仅是一个哈希值:

>>> x = (i for i in [0, 1, 2, 3, 4, 5, 6])
>>> y = (i for i in [0, 1, 2, 3, 4, 5, 6])
>>> hash(x)
-9223372036852064692
>>> hash(y)
-9223372036852064683
>>> id(x)
43377864
>>> x = (i for i in [0, 1, 2, 3, 4, 5, 6])
>>> id(x)
43378296
>>> hash(x)
-9223372036852064665

至于为什么Python允许你对它们进行哈希处理,这与它允许你对各种对象进行哈希的原因相同:所以你可以将不同的对象分开并将它们用作字典键。它并不是要告诉你生成器做什么,它是什么对象。它没有什么不同:

>>> hash(object())
225805
>>> hash(object())
225805
>>> x = object()
>>> y = object()
>>> hash(x)
225805
>>> hash(y)
225806

如果一个对象是可以清除的,那么它是不可变的这个想法是一种误解。对象可以定义他们想要的任何类型的散列,只要它与它们自己的等式定义兼容即可。例如,用户定义类的实例可以类似于这些生成器函数的方式进行哈希(基于对象id的哈希)。

人们有时似乎认为规则是“如果一个对象是可变的,它是不可混合的”,但这是不正确的。实际规则更像是“如果一个对象不可以清除,那么它是可变的”。 (更具体地说,它定义了一个取决于可变状态的相等概念,或者它明确禁止散列它本身的某些不正当理由。)换句话说,为什么某个可清洗类型可以清除它通常没有意义;只考虑为什么不可用的类型是不可用的,这才有意义。 内置可变类型列表,dict和set具有不可删除的特定原因:它们是基于它们的(可变)值而不是对象id进行比较。但仅仅因为某些其他类型有一些内部状态并不意味着它不能被清洗。您可以定义整天可变且可清除的对象。他们只需要以兼容的方式定义相等性和可持续性。确实无法根据内容对发生器进行相等性比较:

>>> x = (i for i in [0, 1, 2, 3, 4, 5, 6])
>>> y = (i for i in [0, 1, 2, 3, 4, 5, 6])
>>> x == y
False

对于生成器,基于值的散列将是唯一无意义的,因为这些值在它们实际从生成器中生成之前不存在。如果散列基于值,则无法对生成器进行散列,例如,使用随机数来决定接下来要产生什么。

答案 2 :(得分:0)

Python object允许哈希和相等:hash(object())

“unhashtable类型”是核心可变集合(即list上的有目的限制,具有已定义的内容相等性比较< / em>因为这可以避免一些常见的编程错误。最值得注意的是,当用作字典键时。

但是,生成器对象的内容相等性比较与身份比较相同gen == gen 暗示 gen is gen。这与使用hash/== object()的结果没有什么不同:它通常没用,如果是,则需要identity-hash / equality。

对于未来的具体化内容,哈希或生成器相等的结果是独立。因此,它没有内容有用的==,因此无需实现内容有用的hash。如果它应该是一个'unhashtable类型',那么它最多会成为一个灰色区域,因为它不再比object()更有用(或更确切地说,有用)作为字典键。

因此,允许为生成器的哈希值返回任何值(即使它在不同的生成器实例之间重复,只要它满足约束hash(x) == hash(x) - &gt; x == x) ,只要它与给定的生成器实例一致(如gen == gen - &gt; gen is gen)。

作为一个实现细节,我希望生成器的哈希使用与基础对象相同的(如in,not overloaded)或类似的实现。

答案 3 :(得分:0)

  

生成器表达

     

返回迭代器

的表达式

如果迭代器是 hashable ,则生成器表达式不应该是例外。迭代器是有状态的,不确定 mutable 。或有状态意味着可变?更重要的是,迭代器只是对象/值工厂,仅此而已:

>>> l = [1, 2]
>>> it = iter(l)
>>> l[:] = [4, 5]
>>> next(it)
4

迭代器未绑定到列表l的初始值。因此,您的生成器表达式不会相对于range(2)[1, 2, 3, 4, 5, 6]中的值进行哈希处理。

散列到相同的值显然取决于解释器,并且很可能是因为您没有对生成器表达式的任何引用。我对每个人都有不同的结果:

>>> hash(i for i in range(2))
-2143682330
>>> hash(i for i in range(2))
-2143687189
>>> hash(i for i in [1, 2, 3, 4, 5, 6])
-2143679383

如果您将生成器表达式命名,则可能会得到不同的值:

>>> ge1 = (i for i in range(2))
>>> ge2 = (i for i in [1, 2, 3, 4, 5, 6])
>>> hash(ge1)
3801919
>>> hash(ge2)
-2143681547