geekdoc-python-zh/docs/pythonlibrary/python-an-intro-to-caching.md

151 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Python:缓存介绍
> 原文:<https://www.blog.pythonlibrary.org/2016/02/25/python-an-intro-to-caching/>
缓存是一种存储有限数据量的方式,以便将来可以更快地检索对所述数据的请求。在本文中,我们将看一个简单的例子,它为我们的缓存使用了一个字典。然后我们将继续使用 Python 标准库的 **functools** 模块来创建缓存。让我们首先创建一个将构造我们的缓存字典的类,然后我们将根据需要扩展它。代码如下:
```py
########################################################################
class MyCache:
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.cache = {}
self.max_cache_size = 10
```
这个类的例子没有什么特别的地方。我们只是创建一个简单的类,并设置两个类变量或属性, **cache****max_cache_size** 。缓存只是一个空字典,而另一个是不言自明的。让我们充实这段代码,让它真正做点什么:
```py
import datetime
import random
########################################################################
class MyCache:
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.cache = {}
self.max_cache_size = 10
#----------------------------------------------------------------------
def __contains__(self, key):
"""
Returns True or False depending on whether or not the key is in the
cache
"""
return key in self.cache
#----------------------------------------------------------------------
def update(self, key, value):
"""
Update the cache dictionary and optionally remove the oldest item
"""
if key not in self.cache and len(self.cache) >= self.max_cache_size:
self.remove_oldest()
self.cache[key] = {'date_accessed': datetime.datetime.now(),
'value': value}
#----------------------------------------------------------------------
def remove_oldest(self):
"""
Remove the entry that has the oldest accessed date
"""
oldest_entry = None
for key in self.cache:
if oldest_entry is None:
oldest_entry = key
elif self.cache[key]['date_accessed'] < self.cache[oldest_entry][
'date_accessed']:
oldest_entry = key
self.cache.pop(oldest_entry)
#----------------------------------------------------------------------
@property
def size(self):
"""
Return the size of the cache
"""
return len(self.cache)
```
这里我们导入了**日期时间**和**随机**模块,然后我们看到了我们之前创建的类。这一次,我们添加了一些方法。其中一种方法是一种叫做**_ _ 包含 __** 的魔法方法。我有点滥用它,但基本思想是,它将允许我们检查类实例,看看它是否包含我们正在寻找的键。 **update** 方法将使用新的键/值对更新我们的缓存字典。如果达到或超过最大缓存值,它还会删除最旧的条目。 **remove_oldest** 方法实际上删除了字典中最老的条目,在这种情况下,这意味着具有最老访问日期的条目。最后,我们有一个名为 **size** 的属性,它返回我们缓存的大小。
如果您添加以下代码,我们可以测试缓存是否按预期工作:
```py
if __name__ == '__main__':
# Test the cache
keys = ['test', 'red', 'fox', 'fence', 'junk',
'other', 'alpha', 'bravo', 'cal', 'devo',
'ele']
s = 'abcdefghijklmnop'
cache = MyCache()
for i, key in enumerate(keys):
if key in cache:
continue
else:
value = ''.join([random.choice(s) for i in range(20)])
cache.update(key, value)
print("#%s iterations, #%s cached entries" % (i+1, cache.size))
print
```
在这个例子中,我们设置了一组预定义的键,并对它们进行循环。如果键不存在,我们就把它们添加到缓存中。缺少的部分是更新访问日期的方法,但是我将把它留给读者作为练习。如果您运行这段代码,您会注意到当缓存填满时,它开始适当地删除旧的条目。
现在让我们继续,看看使用 Python 内置的 **functools** 模块创建缓存的另一种方式!
* * *
### 使用 functools.lru_cache
functools 模块提供了一个方便的装饰器,叫做 [lru_cache](https://docs.python.org/3/library/functools.html#functools.lru_cache) 。注意是在 3.2 中添加的。根据文档,它将“用一个记忆化的可调用函数来包装一个函数,这个函数可以保存最大大小的最近调用”。让我们根据文档中的例子编写一个快速函数,它将抓取各种网页。在这种情况下,我们将从 Python 文档站点获取页面。
```py
import urllib.error
import urllib.request
from functools import lru_cache
@lru_cache(maxsize=24)
def get_webpage(module):
"""
Gets the specified Python module web page
"""
webpage = "https://docs.python.org/3/library/{}.html".format(module)
try:
with urllib.request.urlopen(webpage) as request:
return request.read()
except urllib.error.HTTPError:
return None
if __name__ == '__main__':
modules = ['functools', 'collections', 'os', 'sys']
for module in modules:
page = get_webpage(module)
if page:
print("{} module page found".format(module))
```
在上面的代码中,我们用 **lru_cache** 来装饰我们的**get _ 网页**函数,并将其最大大小设置为 24 个调用。然后,我们设置一个网页字符串变量,并传入我们希望函数获取的模块。我发现,如果你在 Python 解释器中运行它,效果最好,比如 IDLE。这就要求你对函数运行几次循环。您将很快看到它第一次运行代码时输出相对较慢。但是如果您在同一个会话中再次运行它您会看到它会立即打印出来这表明 lru_cache 已经正确地缓存了调用。在您自己的解释器实例中尝试一下,亲自看看结果。
还有一个**类型的**参数,我们可以将它传递给装饰器。它是一个布尔值,告诉装饰器如果 typed 设置为 True就分别缓存不同类型的参数。
* * *
### 包扎
现在,您已经对使用 Python 编写自己的缓存有所了解。如果您正在进行大量昂贵的 I/O 调用,或者如果您想要缓存诸如登录凭证之类的东西,这是一个有趣的工具,非常有用。玩得开心!