想象一下有一个函数,它处理繁重的计算工作,我们希望在 Tornado 应用程序上下文中异步执行。此外,我们希望通过将其结果存储到磁盘来懒惰地评估该函数,而不是为相同的参数重新运行该函数两次。
在不缓存结果(记忆)的情况下,可以执行以下操作:
def complex_computation(arguments):
...
return result
@gen.coroutine
def complex_computation_caller(arguments):
...
result = complex_computation(arguments)
raise gen.Return(result)
假设要实现函数记忆,我们从joblib中选择Memory类。通过简单地用函数装饰函数可以很容易地记住:@mem.cache
@mem.cache
def complex_computation(arguments):
...
return result
哪里mem
可以像mem = Memory(cachedir=get_cache_dir())
.
现在考虑将两者结合起来,我们在执行器上执行计算复杂的函数:
class TaskRunner(object):
def __init__(self, loop=None, number_of_workers=1):
self.executor = futures.ThreadPoolExecutor(number_of_workers)
self.loop = loop or IOLoop.instance()
@run_on_executor
def run(self, func, *args, **kwargs):
return func(*args, **kwargs)
mem = Memory(cachedir=get_cache_dir())
_runner = TaskRunner(1)
@mem.cache
def complex_computation(arguments):
...
return result
@gen.coroutine
def complex_computation_caller(arguments):
result = yield _runner.run(complex_computation, arguments)
...
raise gen.Return(result)
那么第一个问题是上述方法在技术上是否正确?
现在让我们考虑以下场景:
@gen.coroutine
def first_coroutine(arguments):
...
result = yield second_coroutine(arguments)
raise gen.Return(result)
@gen.coroutine
def second_coroutine(arguments):
...
result = yield third_coroutine(arguments)
raise gen.Return(result)
第二个问题是如何记忆second_coroutine
?这样做是否正确:
@gen.coroutine
def first_coroutine(arguments):
...
mem = Memory(cachedir=get_cache_dir())
mem_second_coroutine = mem(second_coroutine)
result = yield mem_second_coroutine(arguments)
raise gen.Return(result)
@gen.coroutine
def second_coroutine(arguments):
...
result = yield third_coroutine(arguments)
raise gen.Return(result)
[更新 I] 在 Tornado 中缓存和重用函数结果讨论了使用functools.lru_cache
orrepoze.lru.lru_cache
作为第二个问题的解决方案。