2

我有一个执行昂贵操作并且经常被调用的函数;但是,该操作只需要执行一次 - 它的结果可以被缓存。

我尝试制作一个无限生成器,但没有得到预期的结果:

>>> 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"]
4

5 回答 5

6

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

gen = g()
value = next(gen)

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

您可能只想使用全局缓存值。将其作为属性存储函数上可以工作:

def g():
    if not hasattr(g, '_cache'):
        g._cache = 'foo'
    return g._cache
于 2013-07-31T18:05:51.057 回答
4

更好的方法:@functools.lru_cache(maxsize=None). 它已被向后移植到 python 2.7,或者您可以自己编写。

我偶尔会犯下以下罪行:

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

    # do work
    foo.cache = result
    return result
于 2013-07-31T18:09:47.667 回答
2

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

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

用于允许将参数传递给生成器的每次迭代的Python 2.5 或更高版本.send()如下(注意**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
于 2013-07-31T18:29:03.813 回答
1

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

于 2013-07-31T18:07:44.580 回答
0

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

它也有大量的扩展

于 2013-07-31T18:28:25.577 回答