我试图获取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
答案 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显然不喜欢这样,而是选择使用函数的标识来比较(并因此散列)它们。