Hash for Python中的lambda函数

时间:2015-11-30 12:22:34

标签: python python-2.7 hash lambda

我试图获取lambda函数的哈希值。为什么我得到两个值(8746164008739和-9223363290690767077)?为什么lambda函数的散列不总是一个值?

>>> fn = lambda: 1
>>> hash(fn)
-9223363290690767077
>>> fn = lambda: 1
>>> hash(fn)
8746164008739
>>> fn = lambda: 1
>>> hash(fn)
-9223363290690767077
>>> fn = lambda: 1
>>> hash(fn)
8746164008739
>>> fn = lambda: 1
>>> hash(fn)
-9223363290690767077

4 个答案:

答案 0 :(得分:41)

除非它们比较相等[1],否则不能保证两个对象散列到相同的值。

Python函数(包括lambdas)即使它们具有相同的代码也不会相等[2]。例如:

>>> (lambda: 1) == (lambda: 1)
False

实现方面,这种行为是由于函数对象不提供自己的相等运算符。相反,它们继承了使用对象标识的默认标识,即其地址。来自documentation

  

如果未定义__cmp__()__eq__()__ne__()操作,则为class   实例按对象标识(“地址”)进行比较。

以下是您的特定示例中发生的情况:

fn = lambda: 1  # New function is allocated at address A and stored in fn.
fn = lambda: 1  # New function is allocated at address B and stored in fn.
                # The function at address A is garbage collected.
fn = lambda: 1  # New function is allocated at address A and stored in fn.
                # The function at address B is garbage collected.
fn = lambda: 1  # New function is allocated at address B and stored in fn.
                # The function at address A is garbage collected.
...

由于地址A始终被散列为一个值,并且地址B与另一个值相对应,因此您会看到hash(fn)在这两个值之间交替显示。然而,这种交替行为是一种实现假象,并且如果例如垃圾收集器的行为略有不同,则可能会改变一天。

以下见解是由@ruakh提供的:

  

值得注意的是,编写一般流程是不可能的   用于确定两个函数是否相等。 (这是一个   undecidability halting problem的结果。)   此外,两个Python函数即使它们的行为也可以表现不同   代码是相同的(因为它们可能是关闭的   不同但同名的变量)。所以这是有道理的   Python函数不会重载相等运算符:没有办法   实现比默认对象标识更好的东西   比较。

[1]反之通常不正确:比较不等的两个对象可以具有相同的哈希值。这称为hash collision

[2] 调用你的lambdas然后散列结果当然总是给出相同的值,因为hash(1)在一个程序中始终是相同的:

>>> (lambda: 1)() == (lambda: 1)()
True

答案 1 :(得分:10)

lambda函数对象的散列基于其内存地址(在CPython中,这是id函数返回的内容)。这意味着任何两个函数对象都将具有不同的哈希值(假设没有哈希冲突),即使函数包含相同的代码。

为了解释问题中发生的事情,首先请注意,编写fn = lambda: 1会在内存中创建一个新的函数对象,并将名称fn绑定到它。因此,这个新函数将具有与任何现有函数不同的散列值。

重复fn = lambda: 1,您会获得哈希的交替值,因为当fn绑定到创建的函数对象时,fn 以前指向的是Python收集的垃圾。这是因为不再有任何引用(因为名称fn现在指向不同的对象)。

Python解释器然后将这个旧的内存地址重用于通过编写fn = lambda: 1创建的下一个新函数对象。

此行为可能因不同系统和Python实现而异。

答案 2 :(得分:5)

每次执行fn = lambda: 1时,都会创建一个新的函数对象,并将绑定到名称fn的旧对象标记为删除。但Python并不是简单地释放对象,而是将其内存传回操作系统。为了最小化系统调用内存分配并最小化内存碎片,Python尝试尽可能地回收内存。因此,当您第三次创建fn = lambda: 1时,解释器会注意到它有一块方便的RAM,对于新的函数对象而言足够大,因此它使用该块。因此,您的第3个fn最终位于该RAM块中,因此具有与第一个fn相同的ID,因为CPython对象的ID是它们的内存地址。

(正如其他人提到的那样,没有提供__hash__特定实现的任何对象类型的哈希是基于它在CPython中的id。如果一个类没有定义{{1} }或__cmp__方法也不应该定义__eq__操作。

答案 3 :(得分:5)

决定两个函数是否相等是不可能的,因为它是暂停问题的超集。

在理想情况下,比较(因此散列)函数会导致类型错误。 Python显然不喜欢这样,而是选择使用函数的标识来比较(并因此散列)它们。