对Python函数进行哈希以在函数修改时重新生成输出

23 投票
5 回答
1090 浏览
提问于 2025-04-15 22:02

我有一个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进行“备忘录化”,不过我想要的不仅仅是对传入的参数进行处理,而是要考虑到这个函数本身可能会发生变化。

5 个回答

0

第一部分是关于如何把你的查找表进行记忆化和序列化。这部分应该比较简单,可以用一些Python的序列化库来实现。第二部分是,当你的源代码发生变化时,你想要删除已经序列化的查找表。也许这个问题被想得太复杂了。一般来说,当你修改代码时,应该会把它提交到某个地方吧?为什么不在提交的过程中加一个钩子,自动删除你的序列化表呢?或者如果这不是研究数据,而是生产环境中的数据,那就把这个过程放到你的发布流程中:如果你的文件的版本号发生了变化(把这个功能放在一个单独的文件里),那么你的发布脚本就会删除序列化的查找表。

1

与其把函数放在一个字符串里,不如把这个函数放在一个单独的文件里。比如可以叫它 time_consuming.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,确保所有的保存和加载功能都能正常工作,然后再把那些慢的部分加回去。

6

如果我理解你的问题,我会这样处理。虽然这个方法有点不太正统,但我觉得它比这里其他的解决方案更可靠,更直接。

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

撰写回答