缓存异步函数时忽略参数
Ignoring a parameter while caching asyncio functions
所以在aiohttp中建议重用ClientSession来避免开销。
所以说我有这样的东西:
async def get_id_from_url(session, url):
async with session.get(url) as response:
return (await response.json())['id'] if response.ok else ''
async def get_ids(urls: List[str]):
async with aiohttp.ClientSession() as session:
ids = await asyncio.gather(*[get_id_from_url(session, url) for url in urls])
print(ids)
我想缓存 get_id_from_url
函数,我看了很多 questions/packages 与缓存异步函数相关的内容:aiocache
还有这个 answer.
但是有一个问题,我不希望缓存依赖于会话参数,我只希望它依赖于 url 参数。
我该怎么做?
经过大量研究后,我找到了 cachetools
库和这个 pull request。
我刚刚从那里得到了解决方案:这是对 cachetools
的 asyncio 支持的实现。并且 cachetools
已经允许您传递一个关键参数,该参数允许您选择函数的哪些参数将参与散列。
import functools
import inspect
import logging
from cachetools.keys import hashkey
logger = logging.getLogger(__name__)
class NullContext(object):
"""A class for noop context managers."""
def __enter__(self):
"""Return ``self`` upon entering the runtime context."""
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
async def __aenter__(self):
"""Return ``self`` upon entering the runtime context."""
return self
async def __aexit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
def aiocached(cache, key=hashkey, lock=None):
"""Decorator to wrap a function or a coroutine with a memoizing callable.
When ``lock`` is provided for a standard function, it's expected to
implement ``__enter__`` and ``__exit__`` that will be used to lock
the cache when it gets updated. If it wraps a coroutine, ``lock``
must implement ``__aenter__`` and ``__aexit__``.
"""
lock = lock or NullContext()
def decorator(func):
if not inspect.iscoroutinefunction(func):
raise RuntimeError('Use aiocached only with async functions')
async def wrapper(*args, **kwargs):
fk = key(*args, **kwargs)
async with lock:
fval = cache.get(fk)
# cache hit
if fval is not None:
return fval
# cache miss
fval = await func(*args, **kwargs)
try:
async with lock:
cache[fk] = fval
except ValueError:
logger.debug('Failed to cache {0}'.format(fval))
return fval
return functools.wraps(func)(wrapper)
return decorator
现在您可以像 cached
一样使用 aiocached
装饰器
所以在aiohttp中建议重用ClientSession来避免开销。
所以说我有这样的东西:
async def get_id_from_url(session, url):
async with session.get(url) as response:
return (await response.json())['id'] if response.ok else ''
async def get_ids(urls: List[str]):
async with aiohttp.ClientSession() as session:
ids = await asyncio.gather(*[get_id_from_url(session, url) for url in urls])
print(ids)
我想缓存 get_id_from_url
函数,我看了很多 questions/packages 与缓存异步函数相关的内容:aiocache
还有这个 answer.
但是有一个问题,我不希望缓存依赖于会话参数,我只希望它依赖于 url 参数。
我该怎么做?
经过大量研究后,我找到了 cachetools
库和这个 pull request。
我刚刚从那里得到了解决方案:这是对 cachetools
的 asyncio 支持的实现。并且 cachetools
已经允许您传递一个关键参数,该参数允许您选择函数的哪些参数将参与散列。
import functools
import inspect
import logging
from cachetools.keys import hashkey
logger = logging.getLogger(__name__)
class NullContext(object):
"""A class for noop context managers."""
def __enter__(self):
"""Return ``self`` upon entering the runtime context."""
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
async def __aenter__(self):
"""Return ``self`` upon entering the runtime context."""
return self
async def __aexit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
def aiocached(cache, key=hashkey, lock=None):
"""Decorator to wrap a function or a coroutine with a memoizing callable.
When ``lock`` is provided for a standard function, it's expected to
implement ``__enter__`` and ``__exit__`` that will be used to lock
the cache when it gets updated. If it wraps a coroutine, ``lock``
must implement ``__aenter__`` and ``__aexit__``.
"""
lock = lock or NullContext()
def decorator(func):
if not inspect.iscoroutinefunction(func):
raise RuntimeError('Use aiocached only with async functions')
async def wrapper(*args, **kwargs):
fk = key(*args, **kwargs)
async with lock:
fval = cache.get(fk)
# cache hit
if fval is not None:
return fval
# cache miss
fval = await func(*args, **kwargs)
try:
async with lock:
cache[fk] = fval
except ValueError:
logger.debug('Failed to cache {0}'.format(fval))
return fval
return functools.wraps(func)(wrapper)
return decorator
现在您可以像 cached
aiocached
装饰器