Python 3.x 如何缓存异步协同路由
我正在使用python 3.4生成一个简单的HTTP请求,如下所示:Python 3.x 如何缓存异步协同路由,python-3.x,python-asyncio,Python 3.x,Python Asyncio,我正在使用python 3.4生成一个简单的HTTP请求,如下所示: response = yield from aiohttp.get(url) @functools.lru_cache(maxsize=128) def cached_request(url): return aiohttp.get(url) 应用程序一次又一次地请求相同的URL,所以我自然希望缓存它。我的第一次尝试是这样的: response = yield from aiohttp.get(url) @fun
response = yield from aiohttp.get(url)
@functools.lru_cache(maxsize=128)
def cached_request(url):
return aiohttp.get(url)
应用程序一次又一次地请求相同的URL,所以我自然希望缓存它。我的第一次尝试是这样的:
response = yield from aiohttp.get(url)
@functools.lru_cache(maxsize=128)
def cached_request(url):
return aiohttp.get(url)
对cached_request
的第一次调用工作正常,但在以后的调用中,我最终使用None
而不是response对象
我对asyncio还比较陌生,所以我尝试了很多组合asyncio.coroutine
decorator、从
中获得收益以及其他一些东西,但似乎都不起作用
那么缓存协同程序是如何工作的呢?我自己编写了一个简单的缓存装饰程序:
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():
....
这类作品。但很多方面都有可能得到改进。例如:如果在第一次调用返回之前第二次调用缓存函数,它将执行第二次。我对aiohttp不太熟悉,因此我不确定到底发生了什么会导致Nones返回,但lru缓存装饰器不会与异步函数一起工作 我使用一个装饰师,它做的基本上是相同的事情;请注意,它与上面tobib的decorator不同,因为它总是返回未来或任务,而不是值:
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缓存一样,我想它应该是私有的,所以最好将它复制过来。lru decorator的另一个变体,它缓存尚未完成的协程,对于对同一密钥的并行请求非常有用:
import asyncio
from collections import OrderedDict
from functools import _make_key, wraps
def async_cache(maxsize=128, event_loop=None):
cache = OrderedDict()
if event_loop is None:
event_loop = asyncio.get_event_loop()
awaiting = dict()
async def run_and_cache(func, args, kwargs):
"""await func with the specified arguments and store the result
in cache."""
result = await func(*args, **kwargs)
key = _make_key(args, kwargs, False)
cache[key] = result
if len(cache) > maxsize:
cache.popitem(False)
cache.move_to_end(key)
return result
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
key = _make_key(args, kwargs, False)
if key in cache:
return cache[key]
if key in awaiting:
task = awaiting[key]
return await asyncio.wait_for(task, timeout=None, loop=event_loop)
task = asyncio.ensure_future(run_and_cache(func, args, kwargs), loop=event_loop)
awaiting[key] = task
result = await asyncio.wait_for(task, timeout=None, loop=event_loop)
del awaiting[key]
return result
return wrapper
return decorator
async def test_async_cache(event_loop):
counter = 0
n, m = 10, 3
@async_cache(maxsize=n, event_loop=event_loop)
async def cached_function(x):
nonlocal counter
await asyncio.sleep(0) # making event loop switch to other coroutine
counter += 1
return x
tasks = [asyncio.ensure_future(cached_function(x), loop=event_loop)
for x in list(range(n)) * m]
done, pending = await asyncio.wait(tasks, loop=event_loop, timeout=1)
assert len(done) == n * m
assert counter == n
event_loop = asyncio.get_event_loop()
task = asyncio.ensure_future(test_async_cache(event_loop))
event_loop.run_until_complete(task)
可能有点晚了,但我已经启动了一个新的包,可能会有所帮助:。欢迎提供意见/评论 例如:
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
后端:)。要使用functools.lru\u缓存
和协同程序,以下代码可以工作
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)
我认为最简单的方法是使用() 并在代码中使用它:
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")
从aiohttp\u缓存导入缓存,设置\u缓存
@cache()#这里有一个流行的异步版本lru\u cache
:尝试在python中缓存异步函数
它还支持参数为用户定义的
或对象类型
或不可损坏的
类型的函数,这在functools.lru cache
或async\u lru
中都不受支持
用法:
pip install async-cache
这就是我认为使用内置的lru\u缓存
和futures最容易实现的方式:
导入异步IO
导入功能工具
#无参数装饰器
def async_lru_cache_decorator(异步函数):
@functools.lru\u缓存
def缓存的异步函数(*args,**kwargs):
协程=异步函数(*args,**kwargs)
返回asyncio。确保未来(协同程序)
返回缓存的异步函数
#具有选项的装饰器
def异步lru缓存(*lru缓存参数,**lru缓存参数):
def async_lru_cache_decorator(异步函数):
@functools.lru_cache(*lru_cache_args,**lru_cache_kwargs)
def缓存的异步函数(*args,**kwargs):
协程=异步函数(*args,**kwargs)
返回asyncio.确保\u future(协同路由)
返回缓存的异步函数
返回异步\u lru\u缓存\u装饰器
@异步lru缓存(maxsize=128)
异步定义您的异步函数(…):。。。
这基本上是将原始函数打包,以便存储它返回的Coroutine
,并将其转换为未来的。通过这种方式,这可以被视为一个常规函数,您可以lru\u cache
-像通常那样执行它
为什么要在将来包装它?Python协同路由是低级构造,您不能多次等待
(您将得到运行时错误:无法重用已经等待的协同路由
)。另一方面,期货是方便的,可以连续等待,并将返回相同的结果
一个警告是,当原始函数引发错误时,缓存未来的
也会缓存。原始的lru\u缓存
不会缓存中断的执行,因此请使用上述解决方案注意此边缘情况
可以进一步调整以合并无参数装饰器和参数化装饰器,如原始的lru\u缓存
,它支持这两种用法。不确定缓存协同程序是什么意思?e、 g.将其保存为变量,以便可以重复调用?是否保存结果,直到在以后执行时替换结果?或者以后重复相同的协同程序?@shongolo我想缓存协同程序的结果。我不熟悉functools.lru_cache(),但如果您只是想返回更新的结果,那么您有什么理由不将更新的结果保存到变量中?然而,当使用异步方法(例如aiohttp.get()
)时,您必须使用一些东西来驱动它。因此,缓存的_请求必须包含在@asyncio.coroutine
中;必须使用来自
的
收益率调用它;return语句应该按照return(从aiohttp.get(url)获得的收益率)
建议:使用an实现lru
行为,即使用OrderedDict。在调用的每个键上移动到\u end
,然后在缓存已满时执行OrderedDict.popitem
。您可以使用信号量将其限制为一次执行。这有点危险,因为它依赖于参数的字符串表示形式。最好坚持使用像原始lru_缓存这样的可散列对象。客户端上是否也需要额外的缓存?