Подтвердить что ты не робот

Хеширование функции 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, за исключением вместо (или в дополнение) аргументов, передаваемых в инвариантную функцию, Я хочу учитывать функцию, которая сама изменится.

4b9b3361

Ответ 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. Несколько других ответов уже дают некоторые решения для этого, поэтому я не буду повторять это здесь.