使用函数对象作为字典键

时间:2012-09-07 04:25:02

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

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

# 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工作正常,我宁愿使用它,因为我害怕可能的名称冲突(例如,{{ 1}}函数或嵌套的函数或函数,被另一个具有相同名称的函数替换,等等。)

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

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

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

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

2 个答案:

答案 0 :(得分:3)

我不确定我是否可以解决上述所有问题,但我可以解决其中一个问题 -

我没有看到为什么函数不能被垃圾收集的任何理由。但是,由于您的函数是字典中的键,只要该字典存在,函数的引用计数将永远不会达到零,并且不会被垃圾回收。

我不知道重新加载模块,但是,这似乎是一个你不应该担心的角落案例。模块实际上不是要重新加载...你在某些情况下可以这样做的事实主要是为了在交互式终端中进行调试而不是用在任何真正的代码中。 (据我所知......)

答案 1 :(得分:1)

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