为什么functools.lru_cache打破了这个功能?

时间:2016-04-22 21:31:13

标签: python python-3.x caching higher-order-functions functools

考虑以下函数,它返回一组元素的所有唯一排列:

def get_permutations(elements):
    if len(elements) == 0:
        yield ()
    else:
        unique_elements = set(elements)
        for first_element in unique_elements:
            remaining_elements = list(elements)
            remaining_elements.remove(first_element)
            for subpermutation in get_permutations(tuple(remaining_elements)):
                yield (first_element,) + subpermutation

for permutation in get_permutations((1, 1, 2)):
    print(permutation)

打印

(1, 1, 2)
(1, 2, 1)
(2, 1, 1)

正如所料。但是,当我添加lru_cache装饰器时,会记住该功能:

import functools

@functools.lru_cache(maxsize=None)
def get_permutations(elements):
    if len(elements) == 0:
        yield ()
    else:
        unique_elements = set(elements)
        for first_element in unique_elements:
            remaining_elements = list(elements)
            remaining_elements.remove(first_element)
            for subpermutation in get_permutations(tuple(remaining_elements)):
                yield (first_element,) + subpermutation

for permutation in get_permutations((1, 1, 2)):
    print(permutation)

它打印以下内容:

(1, 1, 2)

为什么只打印第一个排列?

1 个答案:

答案 0 :(得分:13)

lru.cache会记住函数的返回值。您的函数返回一个生成器。发电机具有状态并且可以耗尽(即,您到达它们的末端并且不再产生任何物品)。与未修饰版本的函数不同,每次使用给定的参数集调用函数时,LRU缓存都会为您提供完全相同的生成器对象。它更好,因为它就是它的用途!

但是,您缓存的某些生成器不止一次使用,并且在第二次及以后使用时会部分或完全耗尽。 (他们甚至可能不止一次“在场”。)

为了解释你得到的结果,考虑当elements的长度为0而你yield () ......第一次发生时会发生什么。下次调用这个生成器时,它已经在最后并且根本没有产生任何东西。因此,你的subpermutation循环什么都不做并且没有进一步产生它。由于这是递归中的“触底反复”情况,因此对程序的工作至关重要,而失去它会破坏程序产生预期值的能力。

(1,)的生成器也会被使用两次,这会在第三个结果变为()之前中断。

要查看正在发生的情况,请在您的函数的第一行添加print(elements)(并在主print循环中为for调用添加某种标记,这样您就可以分辨其中的不同之处)。然后比较memoized版本与原始版本的输出。

您似乎想要某种方式来记住生成器的结果。在这种情况下你想要做的是把它写成一个函数,它返回一个包含所有项目的列表(而不是一次产生一个项目)并记住它。