【问题标题】:Why don't functions preserve identity? [closed]为什么函数不保留身份? [关闭]
【发布时间】:2020-04-13 12:18:54
【问题描述】:

我想知道为什么 Python 3.7 函数的行为方式相当奇怪。我认为这与hashability 的整个概念有点奇怪和矛盾。让我用一个简单的示例代码来澄清我遇到的问题。知道元组是可散列的,请考虑以下几点:

a = (-1, 20, 8)
b = (-1, 20, 8)
def f(x):
    return min(x), max(x)

现在让我们检查一下:

>>> print(a is b, a.__hash__() == b.__hash__())
False True
>>> print((-1, 20, 8) is (-1, 20, 8))
True

这很奇怪,但我猜“命名”可散列对象会使它们有所不同(它们的 id() 在变量定义期间发生变化)。功能怎么样? Functions are hashable,对吧?让我们看看:

>>> print(f(a) is f(b))
False
>>> print(id(f(a)) == id(f(b)), f(a).__hash__() == f(b).__hash__())
True True

现在这是我困惑的高潮。你应该感到惊讶,即使f(a) is f(a) 也是False。但怎么会呢?你不觉得这种行为是不正确的,应该由 Python 社区解决和修复吗?

【问题讨论】:

标签: python function tuples equality hashable


【解决方案1】:

你不能保证两个相同的调用是相同的,因为函数在 Python 中也是对象,因此它们可以保持状态。但是,即使您将状态分开,您也不应该依赖 is 将评估 True 如果两个对象的内容相同。

在某些情况下,Python 会优化代码以使用与单例相同的对象,但您不应该对此做出任何假设。

255 is 255 由于 CPython 的实现细节而返回 True,而 256 is 256 返回 False。如果只关心深度平等,请使用==is 专为对象相等性检查而设计。

c = 40
def f(x):
    return c + x

a = 1
f(a)
# 41

c += 1
f(a)
# 42

f(a) is f(a)
# True

c += 500
f(a) is f(a)
# False

f(a) is f(a) 可以产生相同的对象,例如 Python 将最多 255 的整数存储为单例,因此第一个测试返回 True 但是当我们没有这些优化 (c += 500) 时,每个调用都会实例化自己的要返回的对象,现在 f(a) is f(a) 将返回 False

【讨论】:

  • 我可能会以某种方式误导您@rmo,但请查看返回值。那些只是元组,它们必须是元组,对吧?那么为什么他们没有表现得像他们应该的那样呢?
  • @AshkanRanjbar 我已经添加了一些关于 Python 如何在您每次创建一些对象时创建新实例(有时不是)。我会回到你的问题,看看我是否错过了重点,抱歉。
  • @AshkanRanjbar 据我了解,这点与元组相同,Python 每次调用它时都会创建一个新的元组实例,而不管其他是否存在具有相同值的实例。它们的哈希值相同,因此您可以安全地使用 ==,但它们仍然是不同的对象,因此 is 不会像您想象的那样工作。
  • 不,不是==,因为== 调用对象的__equality__,而is 查找对象的id()
【解决方案2】:

is python 中的关键字比较操作数是否指向同一个对象。 Python 提供了id() 函数来返回对象实例的唯一标识符。所以,a is b 不会比较对象是否包含相同的值,它只会返回如果 a 和 b 是相同的对象。

__hash__() 函数根据对象的内容/值返回一个值。

>>> a = (-1, 20, 8)
>>> b = (-1, 20, 8)
>>> id(a)
2347044252768
>>> id(b)
2347044252336
>>> hash(a)
-3789721413161926883
>>> hash(b)
-3789721413161926883

现在最后一个问题,f(a) is f(b) 比较f(a)f(b) 返回的结果是否指向内存中的同一个对象。 如果您的函数 return min(x), max(x) 将返回一个 new 元组,其中包含 x 的最小值和最大值。因此,print(f(a) is f(b)) 为假

f(a).__hash__() == f(b).__hash__() 是 True 因为这实际上比较了结果值的哈希,而不是您认为的函数的哈希。 如果你想要函数的哈希值,你可以使用f.__hash__() 或 hash(f),因为 Python 中的函数只是一个可调用对象。

唯一有趣的部分是 print(id(f(a)) == id(f(b))) 显示 True。这可能是由于 CPython 表达式字节码优化器。

如果单独执行,则返回 False。

>>> c = f(a)
>>> d = f(b)
>>> print(id(f(a)) == id(f(b)))
True
>>> print(id(c) == id(d))
False

我不确定这是否是一个应该修复的错误,但这是一个奇怪的不一致。顺便说一句,我在 Windows 64 位上使用 Python 3.7.2。不同 Python 版本或实现的行为可能会有所不同。

如果用字符串替换整数值,行为也会因 Python 的字符串驻留优化而改变。

因此,这里的课程就像其他语言的一般指南一样,尽可能避免比较对象引用/指针,因为您可能正在研究有关如何引用对象、优化以及可能的 GC 工作方式的一些实现细节。

这是一篇有趣的相关文章:Python Optimization: How it Can Make You a Better Programmer

【讨论】:

  • 这可能是由于 CPython 表达式字节码优化器造成的。 不是。不好的猜测。这不是错误,只是 CPython 重用堆空间(id 基于内存位置)。
  • 好的,但还是有点奇怪。因此,CPython 表达式解析器评估 == 的左侧丢弃对象,然后评估右侧的表达式。我想最常见的比较方法是object.__eq__(self, other),其中两个对象应该同时可用,并且它不应该具有相同的内存位置。这就是我猜为什么它以某种方式优化表达式评估的原因?
猜你喜欢
  • 1970-01-01
  • 2012-04-20
  • 1970-01-01
  • 2013-01-18
  • 2019-04-06
  • 2014-10-31
  • 1970-01-01
  • 2021-12-24
  • 1970-01-01
相关资源
最近更新 更多