Memoize to disk - python - постоянная memoization

Есть ли способ запоминать вывод функции на диск?

У меня есть функция

def getHtmlOfUrl(url):
    ... # expensive computation

и хотел бы сделать что-то вроде:

def getHtmlMemoized(url) = memoizeToFile(getHtmlOfUrl, "file.dat")

а затем вызовите getHtmlMemoized (url), чтобы выполнить дорогостоящее вычисление только один раз для каждого URL-адреса.

Ответы

Ответ 1

Python предлагает очень элегантный способ сделать это - декораторы. В принципе, декоратор - это функция, которая обертывает другую функцию для обеспечения дополнительной функциональности без изменения исходного кода функции. Ваш декоратор можно написать следующим образом:

import json

def persist_to_file(file_name):

    def decorator(original_func):

        try:
            cache = json.load(open(file_name, 'r'))
        except (IOError, ValueError):
            cache = {}

        def new_func(param):
            if param not in cache:
                cache[param] = original_func(param)
                json.dump(cache, open(file_name, 'w'))
            return cache[param]

        return new_func

    return decorator

Как только у вас получится, "украсить" функцию, используя @-syntax, и вы готовы.

@persist_to_file('cache.dat')
def html_of_url(url):
    your function code...

Обратите внимание, что этот декоратор намеренно упрощен и может не работать для каждой ситуации, например, когда исходная функция принимает или возвращает данные, которые не могут быть сериализованы json.

Подробнее о декораторах: Как создать цепочку декораторов функций?

И вот как сделать декоратор сохранить кеш только один раз, во время выхода:

import json, atexit

def persist_to_file(file_name):

    try:
        cache = json.load(open(file_name, 'r'))
    except (IOError, ValueError):
        cache = {}

    atexit.register(lambda: json.dump(cache, open(file_name, 'w')))

    def decorator(func):
        def new_func(param):
            if param not in cache:
                cache[param] = func(param)
            return cache[param]
        return new_func

    return decorator

Ответ 2

Проверьте joblib.Memory. Это библиотека для именно этого.

Ответ 3

Более чистое решение на базе модуля Python Shelve. Преимущество заключается в том, что кэш обновляется в режиме реального времени с помощью хорошо известного синтаксиса dict, а также является доказательством исключений (нет необходимости обрабатывать надоедливую KeyError).

import shelve
def shelve_it(file_name):
    d = shelve.open(file_name)

    def decorator(func):
        def new_func(param):
            if param not in d:
                d[param] = func(param)
            return d[param]

        return new_func

    return decorator

@shelve_it('cache.shelve')
def expensive_funcion(param):
    pass

Это облегчит вычисление функции только один раз. Следующие последующие вызовы вернут сохраненный результат.

Ответ 4

Что-то вроде этого должно делать:

import json

class Memoize(object):
    def __init__(self, func):
        self.func = func
        self.memo = {}

    def load_memo(filename):
        with open(filename) as f:
            self.memo.update(json.load(f))

    def save_memo(filename):
        with open(filename, 'w') as f:
            json.dump(self.memo, f)

    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.func(*args)
        return self.memo[args]

Основное использование:

your_mem_func = Memoize(your_func)
your_mem_func.load_memo('yourdata.json')
#  do your stuff with your_mem_func

Если вы хотите записать свой "кеш" в файл после его использования - для повторного загрузки в будущем:

your_mem_func.save_memo('yournewdata.json')

Ответ 5

Библиотека Artemis имеет для этого модуль. (вам нужно pip install artemis-ml)

Вы украшаете свою функцию:

from artemis.fileman.disk_memoize import memoize_to_disk

@memoize_to_disk
def fcn(a, b, c = None):
    results = ...
    return results

Внутренне это делает хэш из входных аргументов и сохраняет memo файлы с помощью этого хэша.

Ответ 6

Предполагая, что данные json сериализуемы, этот код должен работать

import os, json

def json_file(fname):
    def decorator(function):
        def wrapper(*args, **kwargs):
            if os.path.isfile(fname):
                with open(fname, 'r') as f:
                    ret = json.load(f)
            else:
                with open(fname, 'w') as f:
                    ret = function(*args, **kwargs)
                    json.dump(ret, f)
            return ret
        return wrapper
    return decorator

decorate getHtmlOfUrl, а затем просто назовите его, если он был запущен ранее, вы получите свои кэшированные данные.

Проверено с помощью python 2.x и python 3.x

Ответ 7

Вы можете использовать пакет cache_to_disk:

    from cache_to_disk import cache_to_disk

    @cache_to_disk(3)
    def my_func(a, b, c, d=None):
        results = ...
        return results

Это будет кэшировать результаты в течение 3 дней, специфичные для аргументов a, b, c и d. Результаты сохраняются в файле pickle на вашем компьютере, а затем выбираются и возвращаются при следующем вызове функции. Через 3 дня файл рассола будет удален до повторного запуска функции. Функция будет перезапускаться всякий раз, когда функция вызывается с новыми аргументами. Более подробная информация здесь: https://github.com/sarenehan/cache_to_disk