Bog*_*kiy 25 python hash lambda python-2.7
我正在尝试获取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
Run Code Online (Sandbox Code Playgroud)
NPE*_*NPE 41
除非它们比较相等[1],否则不保证两个对象散列到相同的值.
Python函数(包括lambdas)即使具有相同的代码也不会相等[2].例如:
>>> (lambda: 1) == (lambda: 1)
False
Run Code Online (Sandbox Code Playgroud)
实现方面,这种行为是由于函数对象不提供自己的相等运算符.相反,它们继承了使用对象标识的默认标识,即其地址.从文档:
如果否
__cmp__(),__eq__()或者__ne__()定义了操作,则通过对象标识("地址")比较类实例.
以下是您的特定示例中发生的情况:
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.
...
Run Code Online (Sandbox Code Playgroud)
由于地址A始终被散列为一个值,并且地址B到另一个值,因此您看到hash(fn)两个值之间存在交替.然而,这种交替行为是一种实现假象,并且如果例如使垃圾收集器的行为略有不同,则可以改变一天.
以下见解是由@ruakh提供的:
值得注意的是,不可能编写一个通用的过程来确定两个函数是否相等.(这是暂停问题不可判定性的结果.)此外,两个Python函数的行为可能不同,即使它们的代码是相同的(因为它们可能是闭包引用不同但相同命名的变量).因此,Python函数不会重载相等运算符是有道理的:没有办法比默认的对象 - 身份比较更好地实现任何东西.
[1]反过来通常不正确:比较不等的两个对象可以具有相同的哈希值.这称为哈希冲突.
[2] 调用 lambdas然后散列结果当然总会给出相同的值,因为hash(1)在一个程序中总是相同的:
>>> (lambda: 1)() == (lambda: 1)()
True
Run Code Online (Sandbox Code Playgroud)
Ale*_*ley 10
lambda函数对象的散列基于其内存地址(在CPython中,这是id函数返回的内容).这意味着任何两个函数对象都将具有不同的哈希值(假设没有哈希冲突),即使函数包含相同的代码.
为了解释问题中发生的事情,首先要注意写入fn = lambda: 1在内存中创建一个新的函数对象并将名称绑定fn到它.因此,此新函数将具有与任何现有函数不同的散列值.
重复fn = lambda: 1,你得到哈希的交替值,因为当fn绑定到新创建的函数对象时,fn 之前指向的函数是Python的垃圾收集.这是因为不再有任何引用(因为名称fn现在指向不同的对象).
然后,Python解释器将这个旧的内存地址重用于通过编写创建的下一个新函数对象fn = lambda: 1.
不同系统和Python实现之间的行为可能不同.
每次执行时fn = lambda: 1都会创建一个新的函数对象,并且绑定到该名称的旧对象将fn被标记为删除.但Python并不是简单地释放对象,而是将其内存传回操作系统.为了最小化系统调用内存分配并最小化内存碎片,Python尝试尽可能地回收内存.因此,当你fn = lambda: 1第三次创建时,解释器注意到它有一块RAM方便,对于新的函数对象来说足够大,所以它使用了那个块.因此,你的第3个fn结束于那个RAM块,因此具有与第一个相同的id fn,因为CPython对象的id是它们的内存地址.
(正如其他人提到的那样,没有提供特定实现的任何对象类型的哈希__hash__是基于它在CPython中的id.如果一个类没有定义一个__cmp__或者__eq__方法,它也不应该定义一个__hash__操作).
决定两个函数是否相等是不可能的,因为它是暂停问题的超集.
在理想情况下,比较(因此散列)函数会导致类型错误.Python显然不喜欢这样,而是选择使用函数的标识来比较(并因此散列)它们.
| 归档时间: |
|
| 查看次数: |
1893 次 |
| 最近记录: |