【问题标题】:Using function objects as a dictionary keys使用函数对象作为字典键
【发布时间】:2026-01-23 01:20:04
【问题描述】:

我使用函数对象作为字典键。我这样做是因为我需要缓存这些函数的结果。这大致是我正在使用的代码:

# module cache.py:
calculation_cache = {}
def cached(func):
  # func takes input as argument
  def new_function(input):
    try:
      result = calculation_cache[(func, input)]
    except KeyError:
      result = func(input)
      calculation_cache[(func, input)] = result
    return result
  return new_function

# module stuff.py
@cached
def func(s):
  # do something time-consuming to s
  # ...
  return result

我可以使用func.__module__ + func.__name__ 而不是func,但如果func 工作正常,我宁愿使用它,因为我担心可能的名称冲突(例如,对于lambda 函数或嵌套函数或被另一个具有相同名称的函数替换,等等)

这似乎工作正常,但我怀疑这可能会在一些难以测试的情况下导致问题。

例如,我担心某个函数会以某种方式被删除,而另一个函数会重用其内存空间。在这种情况下,我的缓存将无效,但它不会知道。这是一个有效的担忧吗?如果有,有什么办法解决吗?

可以删除函数吗?重新加载模块是否会将函数移动到新地址(从而更改其哈希值,并为新函数释放旧内存地址)?有人可以(出于某种奇怪的原因)简单地删除模块中定义的函数(再次使内存可用于新函数)吗?

如果仅使用def 明确定义的函数执行此操作是安全的,那么我可以禁止使用cached,除非作为装饰器(我不知道如何强制执行它,但我可以记录它在cached 文档字符串中)。

【问题讨论】:

  • python 中的函数和其他所有东西一样都是对象。我怀疑只要您持有对它的引用(作为字典中的键),它就会一直存在。我不确定重新加载会产生什么效果,但我会假设它会创建新的函数对象。
  • 是重新加载创建的新对象。

标签: python function caching python-3.x decorator


【解决方案1】:

您的代码应该可以工作。就像其他人所说的那样,由于函数对象仍然被字典引用,它不会被垃圾收集。该功能可以被删除或模块重新加载。这意味着func 标签将引用新版本的函数对象(或不引用)。但是即使 func 不再引用旧的函数对象,它仍然会在内存中。您的字典将为旧功能和新功能提供单独的条目,这可能对您更有效,因为这意味着您不会获得附加到旧功能的陈旧结果。您甚至可以使用您的字典作为后门来检索旧版本的函数。只需枚举calculation_cache.keys() 即可取回。

【讨论】:

    【解决方案2】:

    我不确定我能否解决您上述所有问题,但我可以解决其中的 1 个问题 --

    我看不出函数不能被垃圾回收的任何原因。但是,由于您的函数是字典中的键,只要该字典存在,您的函数的引用计数就永远不会达到零,并且不会受到垃圾回收的影响。

    我不知道如何重新加载模块,但是,这似乎是一个极端情况,您真的不需要担心。模块并不是真的要重新加载...在某些情况下您可以这样做的事实主要是为了在交互式终端中进行调试,而不是在任何实际代码中使用.. . (据我所知……)

    【讨论】:

    • 我不认为散列函数值会创建对它的引用,因为hash(f) 只是一个数字。我忘记了字典不仅需要哈希,还需要对对象本身的引用,以便检查它是否真的在那里。谢谢!似乎它解决了我的主要问题:我不会删除一个函数。至于模块的重载,有some rare use cases。不过,我似乎不需要担心这个。
    • @max,还保留了一个引用,因此您可以获取键的值(.keys.items 等)
    • @max -- 是的,只要想想yourdict.keys(),你就会立即意识到字典实际上必须存储一个引用。我想它可以使用weakref(但它没有)来做到这一点,因为在哈希冲突的情况下,它也需要有对象。