【问题标题】:How to inform user that cache is being used?如何通知用户正在使用缓存?
【发布时间】:2021-02-22 22:15:41
【问题描述】:

我正在使用 python 库 diskcache 及其装饰器 @cache.memoize 来缓存对我的 couchdb 数据库的调用。工作正常。但是,我想向用户打印数据是从数据库返回还是从缓存返回。

我什至不知道如何解决这个问题。

到目前为止我的代码:

import couchdb
from diskcache import Cache

cache = Cache("couch_cache")


@cache.memoize()
def fetch_doc(url: str, database: str, doc_id: str) -> dict:

    server = couchdb.Server(url=url)
    db = server[database]

    return dict(db[doc_id])

【问题讨论】:

  • 这让我感到困惑,为什么您要减慢旨在加快函数执行的速度。无论如何,要使用一个做你想做的事,你需要编写自己的装饰器。建议您首先查看diskcache.Cache.memoize()(纯Python)的源代码。

标签: python decorator python-decorators diskcache


【解决方案1】:

这是一种方法,但我并不真正推荐它,因为 (1) 它添加了一个额外的手动检查缓存的操作,并且 (2) 它可能复制了库内部已经在做的事情。我没有适当检查任何性能影响,因为我没有具有不同 doc_ids 的生产数据/环境,但正如 martineau's comment 所说,它可能减慢速度,因为额外的查找操作。

但它就是这样。

diskcache.Cache 对象“支持熟悉的 Python 映射接口”(如 dicts)。然后,您可以使用根据memoize-d 函数的参数自动生成的相同密钥,手动检查缓存中是否已经存在给定的密钥:

额外的__cache_key__ 属性可用于生成用于给定参数的缓存键。

>>> key = fibonacci.__cache_key__(100)  
>>> print(cache[key])  
>>> 354224848179261915075    

因此,您可以将 fetch_doc 函数包装到 另一个 函数中,该函数检查是否存在基于 urldatabasedoc_id 参数的缓存键,打印结果给用户,在调用实际的fetch_doc函数之前:

import couchdb
from diskcache import Cache

cache = Cache("couch_cache")

@cache.memoize()
def fetch_doc(url: str, database: str, doc_id: str) -> dict:
    server = couchdb.Server(url=url)
    db = server[database]
    return dict(db[doc_id])

def fetch_doc_with_logging(url: str, database: str, doc_id: str):
    # Generate the key
    key = fetch_doc.__cache_key__(url, database, doc_id)

    # Print out whether getting from cache or not
    if key in cache:
        print(f'Getting {doc_id} from cache!')
    else:
        print(f'Getting {doc_id} from DB!')

    # Call the actual memoize-d function
    return fetch_doc(url, database, doc_id)

在测试时:

url = 'https://your.couchdb.instance'
database = 'test'
doc_id = 'c97bbe3127fb6b89779c86da7b000885'

cache.stats(enable=True, reset=True)
for _ in range(5):
    fetch_doc_with_logging(url, database, doc_id)
print(f'(hits, misses) = {cache.stats()}')

# Only for testing, so 1st call will always miss and will get from DB
cache.clear()

它输出:

$ python test.py 
Getting c97bbe3127fb6b89779c86da7b000885 from DB!
Getting c97bbe3127fb6b89779c86da7b000885 from cache!
Getting c97bbe3127fb6b89779c86da7b000885 from cache!
Getting c97bbe3127fb6b89779c86da7b000885 from cache!
Getting c97bbe3127fb6b89779c86da7b000885 from cache!
(hits, misses) = (4, 1)

你可以把这个包装函数变成一个装饰器:

def log_if_cache_or_not(memoized_func):
    def _wrap(*args):
        key = memoized_func.__cache_key__(*args)
        if key in cache:
            print(f'Getting {doc_id} from cache!')
        else:
            print(f'Getting {doc_id} from DB!')
        return memoized_func(*args)

    return _wrap

@log_if_cache_or_not
@cache.memoize()
def fetch_doc(url: str, database: str, doc_id: str) -> dict:
    server = couchdb.Server(url=url)
    db = server[database]
    return dict(db[doc_id])

for _ in range(5):
    fetch_doc(url, database, doc_id)

或者as suggested in the comments,将其组合成1个新的装饰器:

def memoize_with_logging(func):
    memoized_func = cache.memoize()(func)

    def _wrap(*args):
        key = memoized_func.__cache_key__(*args)
        if key in cache:
            print(f'Getting {doc_id} from cache!')
        else:
            print(f'Getting {doc_id} from DB!')
        return memoized_func(*args)

    return _wrap

@memoize_with_logging
def fetch_doc(url: str, database: str, doc_id: str) -> dict:
    server = couchdb.Server(url=url)
    db = server[database]
    return dict(db[doc_id])

for _ in range(5):
    fetch_doc(url, database, doc_id)

一些快速测试:

In [9]: %timeit for _ in range(100000): fetch_doc(url, database, doc_id)
13.7 s ± 112 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [10]: %timeit for _ in range(100000): fetch_doc_with_logging(url, database, doc_id)
21.2 s ± 637 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

(如果doc_id在调用中随机变化可能会更好)

再次,正如我在开头提到的,缓存和memoize-ing 函数调用应该加速该函数。无论您是从数据库还是从缓存中获取数据,这个答案都会增加额外的缓存查找和打印/记录操作,并且它可能会影响该函数调用的性能。适当测试。

【讨论】:

  • 您的装饰器可以作为输入普通函数并通过cache.memoize() 自行传递它们,这样您就不需要 2 个装饰器了。类似def my_memoize(func): memoized_func = cache.memoize()(func)
  • @RoyCohen 啊,是的,这也是可能的。虽然这会将日志记录和记忆功能结合到一个功能中,但我建议让 1 个功能只做一件事。此外,将日志记录作为单独的装饰器,使其可重用且易于插入/拔出。
  • 但是log_if_cache_or_not 需要对其参数进行memoized,因此它使sence memoize 其中的函数。如果您担心可用性,您可以设置一个可选参数来关闭它。
  • 我刚刚想到的另一个想法是仅在尚未记住功能时才记住该功能。我不知道是否有更好的方法,但我认为hasattr(func, '__cache_key__') 会起作用。
猜你喜欢
  • 2018-12-24
  • 1970-01-01
  • 2017-06-15
  • 2011-09-06
  • 2016-03-20
  • 1970-01-01
  • 1970-01-01
  • 2014-10-04
  • 1970-01-01
相关资源
最近更新 更多