Генераторы кэширования в Python

Работа над созданием разделов фиксированного размера в подход динамического программирования,

Я пишу этот кусок кода:

def cache(f):
    memory={}
    def g(*args):
        if args not in memory:
            memory[args]=f(*args)
        return memory[args]
    return g

#@cache   remove # for cached version 
def sum_to_n_list(n, size):
    if size>1 :
        l=[]
        for i in range(n//size,n):
                for tail in sum_to_n_list(n - i, size - 1):
                    if i>=tail[0] : l.append([i]+tail)
        return l
    else : return [[n]]

Например, разбиение 6 на 3 целых числа имеет три решения: 6 = 2+2+2 = 3+2+1 = 4+1+1:

In [9]: sum_to_n_list(6,3)
Out[9]: [[2, 2, 2], [3, 2, 1], [4, 1, 1]]  

Как и ожидалось, кешированная версия работает быстрее:

In [10]: %timeit a=sum_to_n_list(20,10)
1 loops, best of 3: 1.25 s per loop

In [11]: %timeit a=sum_to_n_list(20,10)  # cached version
100 loops, best of 3: 2.38 ms per loop

Я хочу иметь такое же улучшение с генератором:

def sum_to_n_gen(n, size):
    if size>1 :
        for i in range(n//size,n):
                for tail in sum_to_n_gen(n - i, size - 1):
                    if i>=tail[0] : yield [i]+tail
    else : yield [n]

Это работает, но я не могу получить кешированную версию этой функции. Как это сделать ?

In [12]: %timeit a=list(sum_to_n_gen(20,10))
1 loops, best of 3: 1.91 s per loop

РЕДАКТИРОВАТЬ

Спасибо @user2357112 и @thomas-k за полезные замечания и советы.

Моя цель здесь — использовать генератор для ясности, потому что он скрывает бесполезные манипуляции со списками в программе; не как экономия памяти.

Итак, я наконец нахожу это решение:

def gencache(f):
    memory={}
    def g(*args):
        if args not in memory:
                memory[args]=list(f(*args))
        return iter(memory[args])
    return g

@gencache
def sum_to_n_gen(n, size):
    if size>1 :
        for i in range(n//size,n):
                for tail in sum_to_n_gen(n - i, size - 1):
                    if i>=tail[0] : yield [i]+tail
    else : yield [n]

Это улучшает скорость с тем же фактором:

In [23]: %timeit  a=sum_to_n_gen(20,10) #cached version
100 loops, best of 3: 2.61 ms per loop

Мой вопрос сейчас:

  • Эта мемоизация работает, потому что все маленькие подзадачи решаются только один раз, а новый итератор перезаряжается при каждом вызове. но преимущество генератора (вычисление каждого следующего члена по запросу) исчезает и для внешнего вызова. У кого-нибудь есть идея сохранить эту функцию?
  • Является ли уточнение кода признанным качеством генераторов?

Спасибо за весь вклад.


person B. M.    schedule 14.01.2016    source источник
comment
Мемоизация в корне противоречит подходу генератора, поскольку компромиссы дизайна противоположны. Мемоизация заключается в сохранении результатов для обмена места на скорость, в то время как подход генератора заключается в том, чтобы не сохранять результаты. У вас не может быть и того, и другого. В лучшем случае вы можете сделать гибрид, который сохраняет результаты по мере их получения, но логика мемоизации становится намного сложнее, и она дает какие-либо преимущества только в тех случаях использования, когда вы потребляете достаточно генератора, чтобы извлечь выгоду из кеша, но не достаточно, что было бы лучше просто составить списки.   -  person user2357112 supports Monica    schedule 15.01.2016
comment
Поскольку это казалось интересной задачей, вот простой механизм кэширования, который позволяет перебирать генератор несколько раз, лениво добавляя значения в список. Но, как предположил @user2357112, это, вероятно, не то, что вы хотите сделать. Я показываю это, потому что это интересно, а не потому, что вы должны это использовать.   -  person Thomas K    schedule 15.01.2016
comment
Большое спасибо пользователю 2357112 и thomas-k. Редактирую свой пост соответственно, с новыми допросами...   -  person B. M.    schedule 15.01.2016