Python 中 lambda 函数的哈希

问题描述 投票:0回答:6

我正在尝试获取 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
python python-2.7 hash lambda
6个回答
43
投票

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

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

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

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

如果没有定义

__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 提供:

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

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

[2] 调用你的 lambdas 然后散列结果当然会总是给出相同的值,因为

hash(1)
在一个程序中总是相同的:

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

10
投票

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

为了解释问题中发生的事情,首先请注意编写

fn = lambda: 1
会在内存中创建一个新的函数对象并将名称
fn
绑定到它。因此,这个新函数将具有与任何现有函数不同的哈希值。

重复

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

Python 解释器然后将这个旧内存地址重新用于下一个通过编写

fn = lambda: 1
.

创建的新函数对象

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


5
投票

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

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


5
投票

每次你做

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

(正如其他人所提到的,任何不提供

__hash__
特定实现的对象类型的散列是基于它在 CPython 中的 id。如果一个类没有定义
__cmp__
__eq__
方法,它应该也没有定义
__hash__
操作)。


0
投票

在 python3 中获取足够的 lambda 内容的依赖于实现的方法:

def function_contents(func):
    closure = tuple(cell.cell_contents for cell in func.__closure__) if func.__closure__ else ()
    return (func.__name__, func.__defaults__, closure, func.__code__.co_code, func.__code__.co_consts)

这包括:

  • lambda 的代码
  • 默认捕获
  • 闭包捕获

应该足以满足您的用例。

你的例子:

fn = lambda: 1
hash(function_contents(fn))
fn = lambda: 1
hash(function_contents(fn))
fn = lambda: 1
hash(function_contents(fn))

...每次都相同的值。


0
投票

hash_func = lambda input_str: hashlib.sha256(input_str.encode()).hexdigest()

© www.soinside.com 2019 - 2024. All rights reserved.