💻

Pythonのキャッシュ機能(functools.cache)を使ってみた

2024/03/26に公開

functools.cacheの概要

functoolsとは関数を操作するためのPythonの標準モジュールです。
functools.cacheはPython 3.9から導入され、デコレータ[1]として機能します。
functools.cacheを使用することで、関数の引数と戻り値をキャッシュし、再度同一の引数で関数を呼び出すとすぐに戻り値を得ることができます。
ちなみに、functools.cachefunctools.lru_cache(maxsize=None)[2]と同等です。

階乗の計算でfunctools.cacheの挙動を確認してみた

functools.cacheのドキュメントに記載されている階乗計算プログラムにて挙動を確認してみます。
https://docs.python.org/ja/3/library/functools.html#functools.cache

f.py
# functoolsモジュールをインポート
import functools

# デコレータfunctools.cacheを使用
@functools.cache
# 階乗n!を計算する関数
def factorial(n):
    # nを標準出力
    print("n = " + str(n))
    # nが0の場合は1を返す
    # nが0以外の場合はn*factorial(n-1)を返す
    return n * factorial(n-1) if n else 1

# factorial(10)を実行
print("factorial(10)")
factorial(10)
# factorial(5)を実行
print("factorial(5)")
factorial(5)
# factorial(12)を実行
print("factorial(12)")
factorial(12)

上記のf.pyを実行すると以下のようになります。

C:\>py f.py
factorial(10)
n = 10
n = 9
n = 8
n = 7
n = 6
n = 5
n = 4
n = 3
n = 2
n = 1
n = 0
factorial(5)
factorial(12)
n = 12
n = 11

C:\>

factorial(10)ではfactorial(10)factorial(0)を呼び出して実行するため、n = 10n = 0が出力されています。
factorial(5)は前述のfactorial(10)の実行によって結果がキャッシュされているため、即座に戻り値を得ることができます。
そのため、n = 5n = 0は出力されません。
factorial(12)は結果がキャッシュされていないfactorial(12)factorial(11)のみ実行することになるため、n = 12n = 11のみ出力されます。

関数の引数はハッシュ可能でなければならない

以下のPythonプログラムをご覧ください。

unhashable.py
# functoolsモジュールをインポート
import functools

# デコレータfunctools.cacheを使用
@functools.cache
# リストの要素を標準出力する関数
def print_list(l):
    for item in l:
        print(item)

# リストを定義
l = ["a", "b", "c"]
# print_list関数を実行
print_list(l)

リストの各要素を標準出力する簡単なプログラムです。
このプログラムを実行してみます。

C:\>py unhashable.py
Traceback (most recent call last):
  File "C:\unhashable.py", line 14, in <module>
    print_list(l)
TypeError: unhashable type: 'list'

C:\>

functools.cacheデコレータを適用する場合、引数はハッシュ可能でなければなりません。
今回、print_list関数の引数はハッシュ可能でないリスト型となっていたため、エラーが発生しました。
次は、引数をハッシュ可能なタプル型にしてみましょう。

hashable.py
# functoolsモジュールをインポート
import functools

# デコレータfunctools.cacheを使用
@functools.cache
# タプルの要素を標準出力する関数
def print_list(t):
    for item in t:
        print(item)

# タプルを定義
t = ("a", "b", "c")
# print_list関数を実行
print_list(t)

このプログラムを実行してみます。

C:\>py test.py
a
b
c

C:\>

正常に実行することができました。

まとめ

  • functools.cacheを使用すると、関数の引数と戻り値をキャッシュすることができるため、実行時間を短縮できます。
  • functools.cacheの適用対象はハッシュ可能な引数をとる関数でなければなりません。
脚注
  1. 関数のデコレータとは関数を別の関数に変換する関数です。ゲシュタルト崩壊しそうです…。 ↩︎

  2. functools.lru_cache(maxsize=N)は直近N回分の関数呼び出しの戻り値をキャッシュするデコレータです。maxsize=Noneとした場合、回数制限が無くなります。 ↩︎

Discussion