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

use*_*284 6 python caching higher-order-functions python-3.x 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)
Run Code Online (Sandbox Code Playgroud)

这打印

(1, 1, 2)
(1, 2, 1)
(2, 1, 1)
Run Code Online (Sandbox Code Playgroud)

正如所料.但是,当我添加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)
Run Code Online (Sandbox Code Playgroud)

它打印以下内容:

(1, 1, 2)
Run Code Online (Sandbox Code Playgroud)

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

kin*_*all 14

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

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

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

生成器for (1,)也使用了两次,这在第三次结果之前就打破了().

要查看正在发生的事情,请print(elements)在函数的第一行添加一个(并print在主for循环中为调用添加某种标记,以便区分).然后比较memoized版本与原始版本的输出.

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

  • 很高兴知道,很容易理解。 (3认同)