【问题标题】:Hashing a python function to regenerate output when the function is modified修改函数时散列python函数以重新生成输出
【发布时间】:2011-02-12 14:07:09
【问题描述】:

我有一个具有确定性结果的 python 函数。运行时间长,输出量大:

def time_consuming_function():
    # lots_of_computing_time to come up with the_result
    return the_result

我不时修改time_consuming_function,但我想避免它在未更改时再次运行。 [time_consuming_function 仅依赖于对于此处考虑的目的不可变的函数;即它可能具有来自 Python 库的函数,但没有来自我要更改的其他代码片段。] 向我建议的解决方案是缓存输出并缓存函数的一些“散列”。如果哈希值发生变化,则函数将被修改,我们必须重新生成输出。

这是可能的还是荒谬的?


更新:根据答案,看起来我想做的是“记忆”time_consuming_function,除了不是(或除了)传递给不变函数的参数之外,我想说明一个本身会发生变化的函数。

【问题讨论】:

  • 如何修改方法?你想在程序运行中保持散列,还是在一次运行中但在某些模块重新加载时保持散列?
  • 我会在脚本文件中使用该方法;我可能会不时手动修改它。该应用程序是该函数将生成“问题数据”以在某些模拟代码中运行。我会不时改变问题。
  • 为什么不直接使用doc来检查函数的变化呢?每次修改功能时只需更改它。它既简单又鼓励(或者更确切地说,迫使)您跟踪您的更改。至于实际的记忆,Mike Graham 为您提供了完美的解决方案。

标签: python caching hash


【解决方案1】:

如果我理解你的问题,我想我会这样解决它。这有点邪恶,但我认为它比我在这里看到的其他解决方案更可靠、更准确。

import inspect
import functools
import json

def memoize_zeroadic_function_to_disk(memo_filename):
    def decorator(f):
        try:
            with open(memo_filename, 'r') as fp:
                cache = json.load(fp)
        except IOError:
            # file doesn't exist yet
            cache = {}

        source = inspect.getsource(f)

        @functools.wraps(f)
        def wrapper():
            if source not in cache:
                cache[source] = f()
                with open(memo_filename, 'w') as fp:
                    json.dump(cache, fp)

            return cache[source]
        return wrapper
    return decorator

@memoize_zeroadic_function_to_disk(...SOME PATH HERE...)
def time_consuming_function():
    # lots_of_computing_time to come up with the_result
    return the_result

【讨论】:

  • 所以唯一进行的散列是 Python 的内部字典键散列,其中键是函数的整个未编译代码的字符串值。有没有办法获取函数的编译代码,所以改变行距或cmets不会导致不同的值?
  • @Seth,是的,我在这里使用 Python 的内部散列是有意义的,因为你真正想要的是比较值(以免你有散列冲突而不知道它。这不太可能但非常可能。)如果您只想缓存最新的函数,我根本不会使用字典或散列,而只是比较值。只是因为你说(带外)你想存储函数的多个版本以便返回旧代码,我才使用字典。
  • @Seth,应该可以存储比整个源代码更少的信息——空格和 cmets 以及像这样完整的信息——但是要确定你有一个充分的必要条件比赛。 f.func_code.co_code 是函数实际存储的字节码,但我不确定我能否向您保证它在编译之间是否相同。我也不完全确定它不会给你误报。
【解决方案2】:

我不会将函数放在字符串中,而是将函数放在自己的文件中。例如,将其称为 time_sumption.py。它看起来像这样:

def time_consuming_method():
   # your existing method here

# Is the cached data older than this file?
if (not os.path.exists(data_file_name) 
    or os.stat(data_file_name).st_mtime < os.stat(__file__).st_mtime):
    data = time_consuming_method()
    save_data(data_file_name, data)
else:
    data = load_data(data_file_name)

# redefine method
def time_consuming_method():
    return data

在测试基础架构以使其正常工作时,我会注释掉速度较慢的部分。制作一个只返回 0 的简单函数,让所有的保存/加载内容都能让您满意,然后将慢速部分放回原处。

【讨论】:

    【解决方案3】:

    第一部分是查找表的记忆和序列化。基于一些 python 序列化库,这应该足够简单。第二部分是您想在源代码更改时删除您的序列化查找表。也许这被过度考虑到了一些奇特的解决方案中。大概当您更改代码时,您会在某处检查它?为什么不在您的签入例程中添加一个挂钩来删除您的序列化表?或者,如果这不是研究数据并且正在生产中,则将其作为发布过程的一部分,如果您的文件的修订号(将此函数放在它自己的文件中)已更改,则您的发布脚本会删除序列化查找表。

    【讨论】:

      【解决方案4】:

      所以,这里有一个非常巧妙的使用装饰器的技巧:

      定义记忆(f): 缓存={}; 定义结果(*args): 如果 args 不在缓存中: 缓存[args]=f(*args); 返回缓存[args]; 返回结果;

      有了以上内容,你就可以使用了:

      @memoize def myfunc(x,y,z): # 一些真正长时间运行的计算

      当您调用 myfunc 时,您实际上将调用它的记忆版本。很整洁吧?每当你想重新定义你的函数时,只需再次使用“@memoize”,或者显式编写:

      myfunc = memoize(new_definition_for_myfunc);

      编辑
      我没有意识到您想在多次运行之间进行缓存。在这种情况下,您可以执行以下操作:

      导入操作系统; 导入 os.path; 进口cPickle; 类 MemoizedFunction(对象): def __init__(self,f): self.function=f; self.filename=str(hash(f))+".cache"; self.cache={}; 如果 os.path.exists(self.filename): 使用 open(filename,'rb') 作为文件: self.cache=cPickle.load(文件); def __call__(self,*args): 如果 args 不在 self.cache 中: self.cache[args]=self.function(*args); 返回 self.cache[args]; def __del__(self): 使用 open(self.filename,'wb') 作为文件: cPickle.dump(self.cache,file,cPickle.HIGHEST_PROTOCOL); 定义记忆(f): 返回 MemoizedFunction(f);

      【讨论】:

      • 非常简洁,是的,但没有回答问题。关键是方法中的代码发生了变化,而不是它的输入参数。
      • 看来 OP 想要记住一个函数在运行时之间的一个返回值。这会根据各种参数在一次运行期间缓存函数的各种返回值。
      • @Mike,好的。我没有意识到这是在程序运行之间。
      • 您当前的版本依赖 hash(f) 在运行之间保持相同,这既不保证也不预期。
      【解决方案5】:

      你描述的实际上是memoization。最常见的功能可以通过定义一个装饰器来记忆。

      一个(过度简化的)例子:

      def memoized(f):
          cache={}
          def memo(*args):
              if args in cache:
                  return cache[args]
              else:
                  ret=f(*args)
                  cache[args]=ret
                  return ret
          return memo
      
      @memoized
      def time_consuming_method():
          # lots_of_computing_time to come up with the_result
          return the_result
      

      编辑:

      从 Mike Graham 的评论和 OP 的更新来看,现在很明显需要在程序的不同运行中缓存值。这可以通过为缓存使用一些持久存储来完成(例如,使用Pickle 或简单的文本文件,或者可能使用完整的数据库,或介于两者之间的任何东西)。选择使用哪种方法取决于 OP 需要什么。其他几个答案已经给出了一些解决方案,所以我不会在这里重复。

      【讨论】:

      • 看来 OP 想要记住一个函数在运行时之间的一个返回值。这会根据各种参数在一次运行期间缓存函数的各种返回值。
      猜你喜欢
      • 1970-01-01
      • 2017-03-06
      • 1970-01-01
      • 2020-10-19
      • 1970-01-01
      • 2014-04-27
      • 2016-04-14
      • 2023-03-23
      • 2019-02-12
      相关资源
      最近更新 更多