计算一次的函数,缓存结果,并从缓存无限返回(Python)

时间:2013-07-31 18:03:12

标签: python caching python-2.7 generator

我有一个执行昂贵操作的功能,经常被调用;但是,操作只需执行一次 - 其结果可以缓存。

我尝试制作一个无限的发电机,但我没有得到我预期的结果:

>>> def g():
...     result = "foo"
...     while True:
...         yield result
... 
>>> g()
<generator object g at 0x1093db230>    # why didn't it give me "foo"?

为什么g不是发电机?

>>> g
<function g at 0x1093de488>

编辑:如果这种方法不起作用,那就没关系了,但我需要一些与常规函数完全相同的东西,如下所示:

>>> [g() for x in range(3)]
["foo", "foo", "foo"]

5 个答案:

答案 0 :(得分:6)

g()是生成器功能。调用它返回生成器。然后,您需要使用该生成器来获取您的值。例如,通过循环或通过调用next()

gen = g()
value = next(gen)

请注意,再次调用g()将再次计算相同的值并生成 new 生成器。

您可能只想使用全局来缓存该值。将其存储为 属性可以起作用:

def g():
    if not hasattr(g, '_cache'):
        g._cache = 'foo'
    return g._cache

答案 1 :(得分:3)

更好的方法:@functools.lru_cache(maxsize=None)。它是backported到python 2.7,或者你可以自己编写。

我偶尔会犯下这样的罪行:

def foo():
    if hasattr(foo, 'cache'):
        return foo.cache

    # do work
    foo.cache = result
    return result

答案 2 :(得分:2)

这是一个死的简单缓存装饰器。它没有考虑参数的任何变化,它只是在第一次调用后返回相同的结果。有更高级的东西可以缓存每个输入组合的结果(“memoization”)。

import functools

def callonce(func):

    result = []

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if not result:
            result.append(func(*args, **kwargs))
        return result[0]

    return wrapper

用法:

@callonce
def long_running_function(x, y, z):
    # do something expensive with x, y, and z, producing result
    return result

如果您因某种原因希望将函数编写为生成器(可能每次调用时结果略有不同,但仍然需要耗时的初始设置,否则您只需要允许C风格的静态变量你的函数要记住从一次调用到下一次调用的一些状态),你可以使用这个装饰器:

import functools

def gen2func(generator):

    gen = []

    @functools.wraps(generator)
    def wrapper(*args, **kwargs):
        if not gen:
            gen.append(generator(*args, **kwargs))
        return next(gen[0])

    return wrapper

用法:

@gen2func
def long_running_function_in_generator_form(x, y, z):
    # do something expensive with x, y, and z, producing result
    while True: 
        yield result
        result += 1    # for example

使用.send()允许将参数传递给生成器的每次迭代的Python 2.5或更高版本如下(请注意,**kwargs不受支持):

import functools

def gen2func(generator):

    gen = []

    @functools.wraps(generator)
    def wrapper(*args):
        if not gen:
            gen.append(generator(*args))
            return next(gen[0])
        return gen[0].send(args)

    return wrapper

@gen2func
def function_with_static_vars(a, b, c):
    # time-consuming initial setup goes here
    # also initialize any "static" vars here
    while True:
        # do something with a, b, c
        a, b, c = yield        # get next a, b, c

答案 3 :(得分:1)

更好的选择是使用memoization。您可以创建一个memoize装饰器,您可以使用它来包装要为其缓存结果的任何函数。你可以找到一些很好的实现here

答案 4 :(得分:0)

您还可以利用Beaker及其缓存。

它也有吨extensions