Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/python-3.x/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 3.x 如何缓存异步协同路由_Python 3.x_Python Asyncio - Fatal编程技术网

Python 3.x 如何缓存异步协同路由

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

我正在使用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)
@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_缓存这样的可散列对象。客户端上是否也需要额外的缓存?