【问题标题】:Hash for lambda function in PythonPython中lambda函数的哈希
【发布时间】:2016-03-04 02:07:17
【问题描述】:

我正在尝试获取 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

【问题讨论】:

  • 为了清楚起见,您确实意识到您正在散列实际的函数对象本身,而不是散列调用时返回的值,对吧?
  • 这可能是个坏主意,但您可以考虑代码对象:hash((lambda : 1).__code__)
  • 只是好奇...您需要此功能用于什么用途的模型?您是否经常根据任意函数在字典中查找内容?
  • @CorleyBrigman 我可以想象有一个set() 的回调,并希望它不会两次保持相同的回调。
  • 另一种情况是持久记忆 - 假设您想将给定输入的长时间运行函数的结果保存到磁盘,以便稍后检索它,您可以使用函数的哈希代码及其输入以引用保存的结果。但正如已经提到的,您确实还必须对所有全局变量和调用函数进行哈希处理才能完美地做到这一点。

标签: python python-2.7 hash lambda


【解决方案1】:

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

Python 函数(包括 lambda)即使具有相同的代码 [2],也不会比较相等。例如:

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

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

如果没有定义__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.
...

由于地址 A 总是散列到一个值,地址 B 散列到另一个值,您会看到 hash(fn) 在两个值之间交替。然而,这种交替行为是一种实现人工制品,并且有一天可能会改变,例如,如果垃圾收集器的行为稍有不同。

@ruakh 提供了以下富有洞察力的注释:

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

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

[2] 调用您的 lambda,然后对结果进行散列处理当然总是会给出相同的值,因为 hash(1) 在一个程序中总是相同的:

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

【讨论】:

  • 关于第一句话,难道对象不比较相等if它们的哈希值相等吗?
  • @muraveill:不,可能存在哈希冲突。
  • @muraveill 对此的混淆可能是由于措辞所致。措辞是正确的,但措辞作为复合语句的一部分可能更有意义:“如果两个对象相等,则它们的哈希值保证相等(根据hash() 的规范)。如果两个对象不相等,那么它们的哈希值可能相等也可能不相等。”
  • +1。还可能值得指出的是,不可能编写一个通用过程来确定两个函数是否等效。 (这是暂停问题的不确定性的结果。)此外,即使它们的代码相同,两个 Python 函数的行为也可能不同(因为它们可能是引用不同但名称相同的变量的闭包)。因此,Python 函数不会重载相等运算符是有道理的:没有办法比默认的对象身份比较更好地实现任何东西。
  • @ruakh 事实上,在标准 ML 或 Haskell 等函数式语言中(它们不认同一切都是指向对象的指针的概念),函数在全部。
【解决方案2】:

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

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

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

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

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

【讨论】:

    【解决方案3】:

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

    (正如其他人提到的,任何不提供__hash__ 特定实现的对象类型的哈希值都是基于它在CPython 中的id。如果一个类没有定义__cmp____eq__ 方法它也不应该定义__hash__ 操作)。

    【讨论】:

      【解决方案4】:

      判断两个函数是否相等是不可能的,因为它是停机问题的超集。

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

      【讨论】:

        猜你喜欢
        • 2010-10-13
        • 2021-11-19
        • 1970-01-01
        • 2011-07-05
        • 2015-02-02
        • 1970-01-01
        • 1970-01-01
        • 2017-01-22
        相关资源
        最近更新 更多