Хеширование функции python для регенерации вывода при изменении функции
У меня есть функция 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, но не из других частей моего кода, которые я бы изменил.] Решение, которое мне предлагает, - это кэширование вывода, а также кэширование некоторой "хэш" функции. Если хеш изменится, функция будет изменена, и нам придется повторно генерировать вывод.
Это возможно или смешно?
Обновлено: на основе ответов, похоже, что я хочу сделать "memoize" time_consuming_function
, за исключением вместо (или в дополнение) аргументов, передаваемых в инвариантную функцию, Я хочу учитывать функцию, которая сама изменится.
Ответы
Ответ 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
Ответ 2
Вместо того, чтобы помещать функцию в строку, я бы поместил ее в свой файл. Например, вызовите его 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, заработает все необходимое для сохранения/загрузки, а затем вернет медленные бит.
Ответ 3
Итак, вот очень аккуратный трюк с использованием декораторов:
def memoize(f):
cache={};
def result(*args):
if args not in cache:
cache[args]=f(*args);
return cache[args];
return result;
С помощью вышеизложенного вы можете использовать:
@memoize
def myfunc(x,y,z):
# Some really long running computation
Когда вы вызываете myfunc, вы действительно будете ссылаться на его мемуазную версию. Довольно аккуратно, да? Всякий раз, когда вы хотите переопределить свою функцию, просто используйте "@memoize" еще раз или явно напишите:
myfunc = memoize(new_definition_for_myfunc);
Изменить
Я не понимал, что вы хотите кэшировать между несколькими запусками. В этом случае вы можете сделать следующее:
import os;
import os.path;
import cPickle;
class MemoizedFunction(object):
def __init__(self,f):
self.function=f;
self.filename=str(hash(f))+".cache";
self.cache={};
if os.path.exists(self.filename):
with open(filename,'rb') as file:
self.cache=cPickle.load(file);
def __call__(self,*args):
if args not in self.cache:
self.cache[args]=self.function(*args);
return self.cache[args];
def __del__(self):
with open(self.filename,'wb') as file:
cPickle.dump(self.cache,file,cPickle.HIGHEST_PROTOCOL);
def memoize(f):
return MemoizedFunction(f);
Ответ 4
Первая часть - это memoization и сериализация вашей таблицы поиска. Это должно быть достаточно простым, основываясь на некоторой библиотеке сериализации python. Вторая часть заключается в том, что вы хотите удалить свою сериализованную таблицу поиска при изменении исходного кода. Возможно, это завышено в какое-то причудливое решение. Предположительно, когда вы меняете код, вы его где-то проверяете? Почему бы не добавить привязку к вашей процедуре проверки, которая удалит вашу сериализованную таблицу? Или, если это не данные исследования и находится в процессе производства, сделайте его частью процесса выпуска, если изменился номер версии вашего файла (поместив эту функцию в собственный файл), ваша версия script удалит сериализованную таблицу поиска.
Ответ 5
То, что вы описываете, эффективно memoization. Большинство общих функций можно запоминать, определяя декоратор.
A (слишком упрощенный) пример:
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
Edit:
Из комментария Майка Грэма и обновления OP теперь ясно, что значения нужно кэшировать по разным прогонам программы. Это можно сделать, используя некоторые из постоянных хранилищ для кеша (например, что-то простое, как использование Pickle
или простой текстовый файл, или, возможно, использование полномасштабной базы данных или что-то среднее между ними). Выбор того, какой метод использовать, зависит от потребностей OP. Несколько других ответов уже дают некоторые решения для этого, поэтому я не буду повторять это здесь.