cachetools

Pythonキャッシュライブラリメモ化デコレータLRUTTLパフォーマンス

GitHub概要

tkem/cachetools

Extensible memoizing collections and decorators

スター2,616
ウォッチ24
フォーク177
作成日:2014年3月22日
言語:Python
ライセンス:MIT License

トピックス

なし

スター履歴

tkem/cachetools Star History
データ取得日時: 2025/10/22 10:04

キャッシュライブラリ

cachetools

概要

cachetoolsは、Pythonの拡張可能なメモ化コレクションとデコレータを提供するライブラリです。

詳細

cachetools(キャッシュツールズ)は、Python標準ライブラリのfunctools.lru_cacheを拡張し、より多様なキャッシュ戦略を提供するライブラリです。2015年にThomas Kemmerによって開発され、メモ化(関数の結果をキャッシュする技術)のためのコレクションとデコレータのセットを提供します。LRU(Least Recently Used)、FIFO(First In First Out)、TTL(Time To Live)、LFU(Least Frequently Used)、TLRUなど、多種多様なキャッシュアルゴリズムを実装しており、用途に応じて最適なキャッシュ戦略を選択できます。@cachedデコレータを使用することで、既存の関数に簡単にキャッシュ機能を追加でき、@cachedmethodデコレータではクラスメソッドのキャッシュも可能です。すべてのキャッシュクラスはcollections.MutableMappingから派生しており、辞書のような使い方ができ、maxsizeとcurrsizeプロパティでキャッシュサイズを管理できます。

メリット・デメリット

メリット

  • 多様なキャッシュアルゴリズム: LRU、FIFO、TTL、LFU、TLRUなど豊富な選択肢
  • デコレータ対応: @cachedと@cachedmethodで簡単にキャッシュ機能を追加
  • 標準ライブラリ互換: functools.lru_cacheとの互換性
  • カスタムキー関数: hashkeyやmethodkeyでカスタムキー生成が可能
  • スレッドセーフティ: ロックオブジェクトと組み合わせて安全な並行処理
  • 統計情報: cache_info()でヒット率やミス数を確認可能
  • メモリ効率: 自動的な期限切れとサイズ制限によるメモリ管理

デメリット

  • スレッドセーフティ: デフォルトではスレッドセーフではない
  • メモリ使用量: 大量のデータをキャッシュする場合のメモリ消費
  • 設定の複雑さ: 複数のキャッシュ戦略から最適なものを選択する必要
  • TTL精度: TTLキャッシュは厳密な時間制御が難しい場合がある
  • 依存関係: 外部ライブラリとしての導入コスト

主要リンク

書き方の例

基本的なキャッシュ

from cachetools import cached, LRUCache, TTLCache

# 辞書を使った基本的なメモ化
@cached(cache={})
def fibonacci(n):
    """フィボナッチ数列の計算をキャッシュ"""
    return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)

# 使用例
print(fibonacci(100))  # 初回は計算時間がかかる
print(fibonacci(100))  # キャッシュから高速で取得

LRUキャッシュ

import urllib.request
from cachetools import cached, LRUCache

# LRUキャッシュでAPIレスポンスをキャッシュ
@cached(cache=LRUCache(maxsize=32))
def get_pep_document(num):
    """Python Enhancement Proposalの取得"""
    url = f'http://www.python.org/dev/peps/pep-{num:04d}/'
    with urllib.request.urlopen(url) as response:
        return response.read()

# 使用例
pep1 = get_pep_document(1)
pep8 = get_pep_document(8)

TTLキャッシュ(期限付き)

from cachetools import cached, TTLCache
import requests

# 10分間キャッシュするAPI呼び出し
@cached(cache=TTLCache(maxsize=1024, ttl=600))
def get_weather_data(city):
    """天気データを期限付きでキャッシュ"""
    url = f'http://api.openweathermap.org/data/2.5/weather?q={city}'
    response = requests.get(url)
    return response.json()

# 使用例
weather = get_weather_data("Tokyo")

キャッシュ統計の確認

from cachetools import cached, LRUCache

@cached(cache=LRUCache(maxsize=32), info=True)
def expensive_function(x):
    """重い計算をシミュレート"""
    import time
    time.sleep(0.1)
    return x * x

# 統計情報を確認
for i in [1, 2, 3, 1, 2, 1]:
    result = expensive_function(i)

print(expensive_function.cache_info())
# CacheInfo(hits=3, misses=3, maxsize=32, currsize=3)

クラスメソッドのキャッシュ

from cachetools import cachedmethod, LRUCache
from cachetools.keys import hashkey
import requests

class APIClient:
    def __init__(self, cache_size=100):
        self.cache = LRUCache(maxsize=cache_size)
    
    @cachedmethod(lambda self: self.cache)
    def fetch_user_data(self, user_id):
        """ユーザーデータをキャッシュ付きで取得"""
        response = requests.get(f'/api/users/{user_id}')
        return response.json()
    
    @cachedmethod(lambda self: self.cache)
    def fetch_post_data(self, post_id):
        """投稿データをキャッシュ付きで取得"""
        response = requests.get(f'/api/posts/{post_id}')
        return response.json()

# 使用例
client = APIClient()
user = client.fetch_user_data(123)
posts = client.fetch_post_data(456)

カスタムキー関数

from cachetools import cached, LRUCache
from cachetools.keys import hashkey

def custom_key(*args, config={}, **kwargs):
    """辞書を含むカスタムキー生成"""
    key = hashkey(*args, **kwargs)
    # 辞書を並べ替えてタプルに変換
    key += tuple(sorted(config.items()))
    return key

@cached(LRUCache(maxsize=128), key=custom_key)
def process_data(data, config={}):
    """設定付きのデータ処理"""
    # 設定に応じたデータ処理
    return f"Processed {data} with {config}"

# 使用例
result1 = process_data("data1", config={"format": "json", "sort": True})
result2 = process_data("data1", config={"sort": True, "format": "json"})  # 同じキーとして認識

複数関数でのキャッシュ共有

from cachetools import cached
from cachetools.keys import hashkey
from functools import partial

# 共有キャッシュ
shared_cache = {}

@cached(shared_cache, key=partial(hashkey, 'fibonacci'))
def fibonacci(n):
    return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)

@cached(shared_cache, key=partial(hashkey, 'lucas'))
def lucas_numbers(n):
    return 2 - n if n < 2 else lucas_numbers(n - 1) + lucas_numbers(n - 2)

# 両方の関数が同じキャッシュを使用
fib_result = fibonacci(10)
lucas_result = lucas_numbers(10)
print(f"共有キャッシュサイズ: {len(shared_cache)}")