返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
Python

Python Caching Mechanism and functools lru_cache

Caching is a processing method that stores a certain amount of data in preparation for subsequent retrieval needs, aiming to speed up data retrieval. The process of generating data may involve operations such as computation, organization, and remote fetching. If the same piece of data needs to be used multiple times, regenerating it each time would greatly waste time. Therefore, if data obtained from operations such as computation or remote requests is cached, subsequent data retrieval needs will be sped up. Let's start with a simple example to understand the concept of a caching mechanism: # -*- coding: utf-8 -*- import random

曦远 (DealiAxy)约 20 分钟
目录1 个章节

Caching is a processing method that stores a certain amount of data in preparation for subsequent retrieval needs, aiming to speed up data retrieval. The process of generating data may involve operations such as computation, organization, and remote fetching. If the same piece of data needs to be used multiple times, regenerating it each time would greatly waste time. Therefore, if data obtained from operations such as computation or remote requests is cached, subsequent data retrieval needs will be sped up.

Let's start with a simple example to understand the concept of a caching mechanism:

# -*- coding: utf-8 -*-

import random
import datetime

class MyCache:
    """缓存类"""

    def __init__(self):
        # 用字典结构以 kv 的形式缓存数据
        self.cache = {}
        # 限制缓存的大小,因为缓存的空间有限
        # 所以当缓存太大时,需要将旧的缓存舍弃掉
        self.max_cache_size = 10

    def __contains__(self, key):
        """根据该键是否存在于缓存当中返回 True 或者 False"""
        return key in self.cache

    def get(self, key):
        """从缓存中获取数据"""
        data = self.cache[key]
        data["date_accessed"] = datetime.datetime.now()
        return data["value"]

    def add(self, key, value):
        """更新该缓存字典,如果缓存太大则先删除最早条目"""
        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):
        """删除具备最早访问日期的输入数据"""
        oldest_entry = None

        for key in self.cache:
            if oldest_entry is None:
                oldest_entry = key
                continue
            curr_entry_date = self.cache[key]['date_accessed']
            oldest_entry_date = self.cache[oldest_entry]['date_accessed']
            if curr_entry_date < oldest_entry_date:
                oldest_entry = key

        self.cache.pop(oldest_entry)

    @property
    def size(self):
        """返回缓存容量大小"""
        return len(self.cache)

if __name__ == '__main__':
    # 测试缓存功能
    cache = MyCache()
    cache.add("test", sum(range(100000)))
    assert cache.get("test") == cache.get("test")

    keys = [
        'red', 'fox', 'fence', 'junk', 'other', 'alpha', 'bravo', 'cal',
        'devo', 'ele'
    ]
    s = 'abcdefghijklmnop'
    for i, key in enumerate(keys):
        if key in cache:
            continue
        else:
            value = ''.join([random.choice(s) for i in range(20)])
            cache.add(key, value)

    assert "test" not in cache
    print(cache.cache)

The example above simply demonstrates the principle of a caching mechanism: data is placed in a dictionary as key-value pairs, so that the next time a value is needed it can be retrieved directly from the dictionary. The implementation of deleting old data in this example is not efficient; in real applications, other approaches can be used.

In Python 3.2, a very elegant caching mechanism was introduced, namely the lru_cache decorator in the functool module, which can directly cache the results of functions or class methods, and subsequent calls then return the cached results directly. The prototype of lru_cache is as follows:

@functools.lru_cache(maxsize=None, typed=False)

Using the lur_cache decorator from the functools module, you can cache up to maxsize call results of this function, thereby improving program execution efficiency, which is especially suitable for time-consuming functions. The parameter maxsize is the maximum number of cached calls; if it is None, there is no limit, and performance is best when set to a power of 2; if typed=True (note that this parameter does not exist in functools32), calls with different argument types will be cached separately, for example f(3) and f(3.0).

LRU (Least Recently Used) is a cache eviction strategy. It performs eviction based on the historical access records of the data; the core idea is, “if data has been accessed recently, then the probability of it being accessed in the future is also higher.” This algorithm was originally a page replacement algorithm for memory management in operating systems, mainly used to find memory blocks in memory that have not been used for a relatively long time and move them out of memory to provide space for new data. Its principle is like the simple example above.

Functions decorated with lru_cache will have two methods, cache_clear and cache_info, used respectively to clear the cache and view cache information. The following is a simple demonstration of using lru_cache:

from functools import lru_cache

@lru_cache(None)
def add(x, y):
    print("calculating: %s + %s" % (x, y))
    return x + y

print(add(1, 2))
print(add(1, 2))
print(add(2, 3))

Output:

calculating: 1 + 2
3
3
calculating: 2 + 3
5

As can be seen from the result, when add(1, 2) is called the second time, the function body is not actually executed; instead, the cached result is returned directly.

To use lru_cahce in Python 2, you need to install the third-party module functools32. There is also a faster third-party module implemented in C that is compatible with both Python2 and Python3, fastcache, which can achieve the same functionality and also supports TTL.

lru_cahce caches data in memory; in fact, data can also be cached on disk. The following example attempts to implement a disk-based cache decorator:

import os
import uuid
import pickle
import shutil
import tempfile
from functools import wraps as func_wraps

class DiskCache(object):
    """缓存数据到磁盘

    实例化参数:
    -----
        cache_path: 缓存文件的路径
    """

    _NAMESPACE = uuid.UUID("c875fb30-a8a8-402d-a796-225a6b065cad")

    def __init__(self, cache_path=None):
        if cache_path:
            self.cache_path = os.path.abspath(cache_path)
        else:
            self.cache_path = os.path.join(tempfile.gettempdir(), ".diskcache")

    def __call__(self, func):
        """返回一个包装后的函数

        如果磁盘中没有缓存,则调用函数获得结果并缓存后再返回
        如果磁盘中有缓存,则直接返回缓存的结果
        """
        @func_wraps(func)
        def wrapper(*args, **kw):
            params_uuid = uuid.uuid5(self._NAMESPACE, "-".join(map(str, (args, kw))))
            key = '{}-{}.cache'.format(func.__name__, str(params_uuid))
            cache_file = os.path.join(self.cache_path, key)

            if not os.path.exists(self.cache_path):
                os.makedirs(self.cache_path)

            try:
                with open(cache_file, 'rb') as f:
                    val = pickle.load(f)
            except Exception:
                val = func(*args, **kw)
                try:
                    with open(cache_file, 'wb') as f:
                        pickle.dump(val, f)
                except Exception:
                    pass
            return val
        return wrapper

    def clear(self, func_name):
        """清理指定函数调用的缓存"""
        for cache_file in os.listdir(self.cache_path):
            if cache_file.startswith(func_name + "-"):
                os.remove(os.path.join(self.cache_path, cache_file))

    def clear_all(self):
        """清理所有缓存"""
        if os.path.exists(self.cache_path):
            shutil.rmtree(self.cache_path)

cache_in_disk = DiskCache()

@cache_in_disk
def add(x, y):
    return x + y

In addition, there are some other caching modules, such as cachelib, cacheout, and so on. When actually needed in practice, you can choose an appropriate caching implementation according to your requirements.

Original

Original: http://kuanghy.github.io/2016/04/20/python-cache

_EOF_

Python Caching Mechanism and functools lru_cache

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

使用Python+FFMPEG实现视频分割与合并

前言 日常中偶尔会遇到需要简单剪辑处理视频的场景,以前我可能会拿出PR来剪辑一下,(别跟我说国产那些软件,剪辑完视频强制加上广告片头片尾恶心的一批),但是PR毕竟太重量级,剪个简单的视频都要花不少时间,直到我发现了FFmpeg…… 这个FFmpeg真的是神器,免费开源功能强大,好像很多软件都是基于这个开发的,它本身提供了命令行接口就已经能满足我们日常的简单视频处理需求,我用来简单处理视频分割、合并

2022-05-27 · Python · 约 1 分钟

自己动手开发简单消息队列(异步任务队列):Python实现

又是造轮子系列咯,Python上有很多成熟完善的异步任务队列框架可以用,比如Celery,或者RQ,不过这些都不自带消息队列服务,都需要使用Redis、RabbitMQ之类的消息队列才行,我用到小项目中又不需要附带这么多东西,于是自己动手来实现咯。 思路 将需要异步执行的任务添加到队列 自动从队列中取出任务,创建新线程执行 保存任务的执行结果和输出 任务完成,调用回调函数,处理返回的数据 使用输出

2021-12-18 · Python · 约 1 分钟

编写爬虫自动下载王者荣耀官网上好看的壁纸

前言 偶然间发现王者荣耀的官网上发布了这么多好看的壁纸。 地址:http://pvp.qq.com/web201605/wallpaper.shtml 看了一下,每一张都挺喜欢的,而且还有十多页,一张一张去慢慢下载肯定不是作为一个Geeker的正确姿势。 由于最近python用得比较多,所以看到什么好玩的就想爬虫下载下来,所以看到这么多好看图片,我又“熟练”地掏出了python。 分析 这个网站长

2021-12-18 · Python · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…