我正在使用aiohttp在python 3.4中创建一个简单的HTTP请求,如下所示:
response = yield from aiohttp.get(url)
应用程序一遍又一遍地请求相同的URL,所以我自然想要缓存它。我的第一次尝试是这样的:
@functools.lru_cache(maxsize=128)
def cached_request(url):
return aiohttp.get(url)
第一次调用cached_request
可以正常工作,但在以后的调用中,我最终会使用None
而不是响应对象。
我对asyncio很陌生,所以我尝试了asyncio.coroutine
装饰器,yield from
和其他一些东西的很多组合,但似乎都没有。
那么缓存协同程序是如何工作的?
答案 0 :(得分:3)
我自己写了一个简单的缓存装饰器:
def async_cache(maxsize=128):
cache = {}
def decorator(fn):
def wrapper(*args):
key = ':'.join(args)
if key not in cache:
if len(cache) >= maxsize:
del cache[cache.keys().next()]
cache[key] = yield from fn(*args)
return cache[key]
return wrapper
return decorator
@async_cache()
@asyncio.coroutine
def expensive_io():
....
这种工作方式。但是很多方面都可能得到改善。例如:如果在第一次调用返回之前第二次调用缓存函数,它将再次执行。
答案 1 :(得分:3)
也许有点晚了,但我已经开始了一个可能有用的新软件包:https://github.com/argaen/aiocache。我们随时欢迎您的贡献/评论。
一个例子:
import asyncio
from collections import namedtuple
from aiocache import cached
from aiocache.serializers import PickleSerializer
Result = namedtuple('Result', "content, status")
@cached(ttl=10, serializer=PickleSerializer())
async def async_main():
print("First ASYNC non cached call...")
await asyncio.sleep(1)
return Result("content", 200)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
print(loop.run_until_complete(async_main()))
print(loop.run_until_complete(async_main()))
print(loop.run_until_complete(async_main()))
print(loop.run_until_complete(async_main()))
请注意,作为额外的,它可以使用Pickle序列化将任何python对象缓存到redis中。如果您只想使用内存,可以使用SimpleMemoryCache
后端:)。
答案 2 :(得分:2)
要将functools.lru_cache
与协同程序一起使用,以下代码可以正常运行。
class Cacheable:
def __init__(self, co):
self.co = co
self.done = False
self.result = None
self.lock = asyncio.Lock()
def __await__(self):
with (yield from self.lock):
if self.done:
return self.result
self.result = yield from self.co.__await__()
self.done = True
return self.result
def cacheable(f):
def wrapped(*args, **kwargs):
r = f(*args, **kwargs)
return Cacheable(r)
return wrapped
@functools.lru_cache()
@cacheable
async def foo():
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
以下是线程安全的
class ThreadSafeCacheable:
def __init__(self, co):
self.co = co
self.done = False
self.result = None
self.lock = threading.Lock()
def __await__(self):
while True:
if self.done:
return self.result
if self.lock.acquire(blocking=False):
self.result = yield from self.co.__await__()
self.done = True
return self.result
else:
yield from asyncio.sleep(0.005)
答案 3 :(得分:0)
我对aiohttp并不熟悉所以我不确定会发生什么会导致Nones被返回,但是lru_cache装饰器不能用于异步函数。
我使用的装饰器基本上是一样的;请注意,与上面的tobib的装饰器不同,它总是会返回一个未来或任务,而不是值:
from collections import OrderedDict
from functools import _make_key, wraps
def future_lru_cache(maxsize=128):
# support use as decorator without calling, for this case maxsize will
# not be an int
try:
real_max_size = int(maxsize)
except ValueError:
real_max_size = 128
cache = OrderedDict()
async def run_and_cache(func, args, kwargs):
"""Run func with the specified arguments and store the result
in cache."""
result = await func(*args, **kwargs)
cache[_make_key(args, kwargs, False)] = result
if len(cache) > real_max_size:
cache.popitem(False)
return result
def wrapper(func):
@wraps(func)
def decorator(*args, **kwargs):
key = _make_key(args, kwargs, False)
if key in cache:
# Some protection against duplicating calls already in
# progress: when starting the call cache the future, and if
# the same thing is requested again return that future.
if isinstance(cache[key], asyncio.Future):
return cache[key]
else:
f = asyncio.Future()
f.set_result(cache[key])
return f
else:
task = asyncio.Task(run_and_cache(func, args, kwargs))
cache[key] = task
return task
return decorator
if callable(maxsize):
return wrapper(maxsize)
else:
return wrapper
我使用functools中的_make_key作为lru_cache,我猜它应该是私有的,所以最好把它复制一遍。
答案 4 :(得分:0)
lru decorator的另一个变种,它缓存尚未完成的协同程序,对同一个密钥的并行请求非常有用:
_KEY
答案 5 :(得分:0)
我认为最简单的方法是使用aiohttp_cache(documentation)
pip install aiohttp-cache
并在代码中使用它:
from aiohttp_cache import cache, setup_cache
@cache() # <-- DECORATED FUNCTION
async def example_1(request):
return web.Response(text="Example")
app = web.Application()
app.router.add_route('GET', "/", example_1)
setup_cache(app) # <-- INITIALIZED aiohttp-cache
web.run_app(app, host="127.0.0.1")
答案 6 :(得分:0)
此处存在lru_cache
的一个流行的异步版本:async_lru
答案 7 :(得分:0)
尝试使用async-cache :pypi async-cache :github在python中缓存异步功能。
它还支持具有 user defined
或 object type
或 unhashable
类型参数的函数 functools.lru_cache
或 async_lru
不支持。
用法:
pip install async-cache
from async-cache import AsyncLRU
@AsyncLRU(maxsize=128)
async def func(*args, **kwargs):
pass
答案 8 :(得分:0)
这是我认为最容易完成的方法,使用内置的 lru_cache
和期货:
import asyncio
import functools
# parameterless decorator
def async_lru_cache_decorator(async_function):
@functools.lru_cache
def cached_async_function(*args, **kwargs):
coroutine = async_function(*args, **kwargs)
return asyncio.ensure_future(coroutine)
return cached_async_function
# decorator with options
def async_lru_cache(*lru_cache_args, **lru_cache_kwargs):
def async_lru_cache_decorator(async_function):
@functools.lru_cache(*lru_cache_args, **lru_cache_kwargs)
def cached_async_function(*args, **kwargs):
coroutine = async_function(*args, **kwargs)
return asyncio.ensure_future(coroutine)
return cached_async_function
return async_lru_cache_decorator
@async_lru_cache(maxsize=128)
async def your_async_function(...): ...
这基本上是获取您的原始函数并将其包装起来,以便我可以存储它返回的 Coroutine
并将其转换为 Future
。这样,这可以被视为常规函数,您可以像往常一样lru_cache
-它。
为什么需要将它包装在 Future 中? Python 协程是低级构造,您不能多次await
(您会得到 RuntimeError: cannot reuse already awaited coroutine
)。另一方面,期货很方便,可以连续等待并返回相同的结果。
需要注意的是,缓存 Future
也会在原始函数引发 Error
时进行缓存。原始 lru_cache
不会缓存中断的执行,因此请注意使用上述解决方案的这种边缘情况。
可以进行进一步调整以合并无参数装饰器和参数化装饰器,例如支持这两种用法的原始 lru_cache
。