この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。
前回は、デコレーターを使用したエラー対応について見ていきました。デコレーターを使うとスマートなエラー処理が書けそうです。

今回もデコレーターの使用例です。キャッシュについて考えてみます。
Pythonのキャッシュ
計算や処理によって出された結果を一時的に保存することをキャッシュといいます。同じ結果を2度目以降求める場合は処理をせずに、保存した内容をそのまま返します。同じ処理の繰り返しを避けることで、全体の処理が早くなったり、全体のリソースの節約になったりします。
デコレーターを使うと、簡単にキャッシュ機能を実現します。以下は簡単な例です。
def deco(func): cache = {} def wrap(*args): if args in cache: print("キャッシュから取り出し") return cache[args] else: print("関数を実行") result = func(*args) cache[args] = result return result return wrap
デコレーターが実行されると、辞書cacheのキーに対象関数で指定した引数があるかを確認します。ない場合には引数をキーとして、対象関数の返りを辞書cacheに登録します。見つかった場合には、関数を実行せずに辞書からキーに対応する値を取り出して返します。
以下はデコレーターの適用例です。
@deco def test_func(x): r = 0 for i in range(1,x): r += i return r print(test_func(100000)) # 関数を実行 # 4999950000 print(test_func(100000)) # キャッシュから取り出し # 4999950000 print(test_func(200000)) # 関数を実行 # 19999900000
この例では、1から指定した整数までの総和を計算します。一度実行した引数については、キャッシュから取り出していることがわかります。キャッシュに保存しているデータは、プロセスが終了するとともに消えてしまいます。また、キャッシュの対象となる関数は、参照透過性(同じ引数を指定すると、同じ処理と計算結果をもたらす性質)が保たれていなければなりません。
フィボナッチ数列
キャッシュを使用した計算処理の例として、フィボナッチ数列の算出がよく取り上げられます。詳細は以下のURLを参照してください。
フィボナッチ数列とは、最初に0、2番目に1、以降は1つ前の数値と2つ前の数値を足し合わせたものを続けるというものです。以下はフィボナッチ数列の指定した位置の数値を返す関数です。
def fibonacci(i): if i == 0 or i == 1: # 最初は0、2番目に1を返す return i else: # 3番目以降は、1つ前の数値と2つ前の数値を足す return fibonacci(i - 1) + fibonacci(i - 2)
3番目以降の数値を算出する部分には、fibonacciが使用されています。この様に関数内で自分自身を呼び出して使用する手法を「再帰処理」といいます。
実際に実行してみると、以下のようになりました。
print(fibonacci(40)) # 102334155 (所用時間23.3秒)
所用時間は僕自身の環境です。かなりの時間を要しました。これは再帰処理で、同じ計算処理を何度も実行しているからです。計算の回数は331160281回でした。引数の値を大きくすると、さらに回数が増えて時間がかかります。
この関数を、先のdeco関数でデコレートしてみましょう(deco関数内のprint文はコメントアウトしておきます)。先頭部分は以下のようになります。
@deco def fibonacci(i):
実行してみると、以下のようになりました。
print(fibonacci(40)) # 102334155 (所用時間0秒)
所用時間は無視できるほどになりました。
functools.cache
この様にキャッシュを使用することで、ムダな計算を省いて処理時間を短縮できます。実際には、今回の様にデコレーターのコードを記述するのではなく、標準モジュール「functools」のcache関数を使用します。以下の様に記述します。
from functools import cache @cache def 関数():
コチラもデコレーターを使った機能です。cache関数を使用したほうが、エラー処理、メモリ管理が最適化されているのでおススメです。使用の際の注意事項は同じで、今回ご紹介した適用例にも使用できます。でも、仕組みを理解しておくことは重要ですね。
次回は無名関数(ラムダ式)
いかがでしょうか。キャッシュは大量の計算処理を実行する際に、力を発揮するようです。次回は無名関数です。手軽に関数を作れる機能の様なのですが、どんな時に使うのでしょうか?お楽しみに!!