Stellen Sie sich vor, Sie haben eine Funktion, die eine schwere Rechenaufgabe behandelt, die wir asynchron in einem Tornado-Anwendungskontext ausführen möchten. Außerdem möchten wir die Funktion faul auswerten, indem wir ihre Ergebnisse auf der Platte speichern und die Funktion nicht zweimal für die gleichen Argumente wiederholen.Kombinieren Tornado gen.coroutine und Joblib mem.cache Dekorateure
Ohne das Ergebnis Zwischenspeichern (memoization) würde man folgendes tun:
def complex_computation(arguments):
...
return result
@gen.coroutine
def complex_computation_caller(arguments):
...
result = complex_computation(arguments)
raise gen.Return(result)
Angenommen Funktion memoization zu erreichen, wählen wir Speicher Klasse von JOBLIB. Durch einfaches Dekorieren der Funktion mit @mem.cache
kann die Funktion leicht memoized werden:
@mem.cache
def complex_computation(arguments):
...
return result
wo mem
etwas wie mem = Memory(cachedir=get_cache_dir())
sein kann.
Betrachten wir nun die Kombination der beiden, wo wir die rechnerisch komplexe Funktion auf eine Executor auszuführen:
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)
Die erste Frage ist, ob der vorgenannte Ansatz ist technisch korrekt?
Nun wollen wir das folgende Szenario vor:
@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)
Die zweite Frage ist, wie man second_coroutine
memoize kann? Ist es richtig, wie etwas zu tun:
@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)
[UPDATE I]Caching and reusing a function result in Tornado bespricht functools.lru_cache
oder repoze.lru.lru_cache
als Lösung für die zweite Frage mit.
Dank Ben. Ja. Du hast recht. Ich habe bereits 'functools.lru_cache' getestet und es funktioniert wie ein Zauber. In Bezug auf dein Beispiel nehme ich an, dass "cache" etwas ist, das durch den äußeren Kontext bereitgestellt wird, da du scheinbar nichts innerhalb des äußeren Decorator-Abschlusses definierst. Nichtsdestoweniger ist Ihr Beispiel vollkommen klar für mich, um die Idee zu begreifen. Prost! –