Python の組み込みキャッシュ デコレーターの使用方法: @lru_cache、@cache、および @cached_property

1.キャッシュテクノロジーが必要な理由は何ですか?

キャッシュの使用は、Python プログラムの速度を最適化する重要な方法の 1 つです適切に使用すると、コンピューティング リソースの負荷が大幅に軽減され、コードの実行が効果的に高速化されます。

Python の組み込みライブラリfunctoolsモジュールには@lru_cache,@cache, @cached_property デコレータが付属しており、非常に使いやすいです。サードパーティのライブラリをインストールする必要がなく、redis などのデータベースにオブジェクトを保存する必要もありません。通常、コードは 1 行だけです。関数の計算結果とクラスのメンバー メソッドの計算を実行するために必要であり、結果はキャッシュされます。

この記事では、これら3つのキャッシュツールの使用手順と例を紹介します。

2.@lru_cacheキャッシュ デコレータの使用

@lru_cache は最も一般的なキャッシュ デコレーターです。lru_cacheの短縮形です。 Last recently used cache入力パラメータと関数の最新の呼び出しの結果をキャッシュできます。新しい呼び出しがある場合は、まずキャッシュに同じ入力パラメータがあるかどうかを確認し、同じ入力パラメータがある場合は、対応する結果を直接返します。パラメーターのない関数の場合、最初の呼び出しの後、後続の呼び出しはすべて、キャッシュされた結果を直接返します。

まずは例を見てみましょう

from functools import lru_cache
from math import sin

@lru_cache
def sin_half(x):
    return sin(x)/2

print('first call result:',sin_half(60))
print('second call result:',sin_half(60))

上記の例では、関数が初めて実行された後、lru_cache は呼び出しパラメーターをキャッシュし、結果を返します。2 回目の実行時に、lru_cache は入力をチェックし、同じ入力パラメータ 60 がキャッシュ内に存在することを確認し、キャッシュから結果を返します。関数が同じ入力に対して計算量の多いタスクを実行する場合、システム リソースを大幅に節約できます。

デコレータパラメータ

lru_cache はデフォルトではキャッシュ内容をクリアしないため、キャッシュは無限に増大し、プログラムが長時間実行されるサービスの場合、メモリ不足になる危険性があります。したがって、maxsize パラメータを追加する必要があります:
@lru_cache(maxsize) パラメータ maxsize は、キャッシュされる最近の呼び出しの数を示します
@lru_cache(360) が示すように、最後の 360 個の関数呼び出し結果のみがキャッシュされます。

@lru_cache(360)
def sin_half(x):
    return sin(x)/2

キャッシュ操作

キャッシュ レポートを表示する:sin_half.cache_info()
キャッシュ コンテンツを強制的にクリアする:sin_half.cache_clear()
もちろん、Python のガベージ コレクションを使用してキャッシュをクリアすることもできます。

以下では、上記のことを示すために 1 つの例を使用しています。

import functools
import gc

# 主要功能: 
# 验证  @lru_cache 装饰器,.chche_info() 和 .cache_clear() 方法的使用
#       garbage collection 的使用

@functools.lru_cache(maxsize = 300) # Max number of Last recently used cache
def fib(n):
	if n < 2:
		return n
	return fib(n-1) + fib(n-2)


fib(30)
fib.cache_clear()

# Before Clearing
print(fib.cache_info())

# After Clearing
print(fib.cache_info())

@functools.lru_cache(maxsize = None)
def gfg1():
    # insert function logic here
    pass

# 再次运行函数 
gfg1()
fib(30)
# garbage collection
gc.collect()

# All objects collected
objects = [i for i in gc.get_objects() 
           if isinstance(i, functools._lru_cache_wrapper)]

print(gfg1.cache_info())

# All objects cleared
for object in objects:
    object.cache_clear()
    
print(gfg1.cache_info())

実行レベルの場合、出力は次のようになります。

CacheInfo(hits=0, misses=0, maxsize=300, currsize=0)
CacheInfo(hits=0, misses=0, maxsize=300, currsize=0)
CacheInfo(hits=0, misses=1, maxsize=None, currsize=1)
CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)

3.@cacheキャッシュデコレータの使用

@lru_cache と比較して、@cache デコレータは軽量、高速、スレッドセーフであり、異なるスレッドで同じ関数を呼び出すことができ、キャッシュされた値を共有できます。

import functools
import time

@functools.cache
def fib(n):
	if n < 2:
		return n
	return fib(n-1) + fib(n-2)
   
if __name__ == '__main__':
    
    start_time = time.time()
    print(fib(400))
    end_time = time.time()
    execution_time_without_cache = end_time - start_time
    print("Time taken without cache: {:.8f} seconds".format(execution_time_without_cache))
    
    start_time = time.time()
    print(fib(400))
    end_time = time.time()
    execution_time_without_cache = end_time - start_time
    print("Time taken with cache: {:.8f} seconds".format(execution_time_without_cache))    

出力:

176023680645013966468226945392411250770384383304492191886725992896575345044216019675
Time taken without cache: 0.00095391 seconds
176023680645013966468226945392411250770384383304492191886725992896575345044216019675
Time taken with cache: 0.00000000 seconds

4.@cached_propertyキャッシュデコレータの使用

@cached_propertyは、クラスのメソッドをプロパティに変換するデコレータで、その値は 1 回だけ計算され、通常のプロパティとしてキャッシュされます。したがって、キャッシュされた結果はインスタンスが存続する限り利用可能であり、次のようにこのメソッドをクラスのプロパティとして使用できます。

调用:    : instance.method
取代旧方式 : instance.method()

@cached_propertyPython の functools モジュールの一部。これはキャッシュに似ていますproperty() @cached_property、追加機能が 1 つあります。

しかし、どうやって実行時間を短縮し、プログラムを高速化するのでしょうか? 次の例を考えてみましょう。

# Without using @cached_property

# A sample class
class Sample():

	def __init__(self, lst):
	self.long_list = lst

	# a method to find the sum of the
	# given long list of integer values
	def find_sum(self):
		return (sum(self.long_list))

# obj is an instance of the class sample
# the list can be longer, this is just
# an example
obj = Sample([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(obj.find_sum())
print(obj.find_sum())
print(obj.find_sum())

出力は次のとおりです。

55
55
55

ここで、1 つの長いリストを渡すと、find_sum() メソッドが呼び出されるたびに指定されたリストの合計が計算されるため、実行に時間がかかり、最終的にプログラムの速度が低下するとします。私たちが望んでいるのは、インスタンスの作成後にリストが変更されないため、メソッドを呼び出して合計にアクセスするたびにリストの合計が計算されるのではなく、リストの合計が 1 回だけ計算されればよいということです。これは @cached_property を使用することで実現できます。

# With using @cached_property

from functools import cached_property

# A sample class
class Sample():
	def __init__(self, lst):
	self.long_list = lst

	# a method to find the sum of the
	# given long list of integer values
	@cached_property
	def find_sum(self):
		return (sum(self.long_list))

# obj is an instance of the class sample
obj = Sample([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(obj.find_sum)
print(obj.find_sum)
print(obj.find_sum)

@cached_property デコレータを使用した後は、再計算する必要はなく、find_sum 値が常にすぐに返されます。
属性を再計算する必要がある場合は、属性を削除するだけで、次の呼び出しで再計算されます。この例では、次のステートメントを追加します。

del obj.find_sum
obj.long_list = range(20,30)
print(obj.find_sum)

コードを実行すると、出力は次のようになります。

55
55
55
245

5. さまざまなキャッシュ方法の適用シナリオ

要約すると、提案は次のとおりです。
1) プログラムのサイズが小さい場合、またはマルチスレッド プログラミングの場合は、@cache デコレータを使用できます。
2) 規模が大きく、長時間実行する場合は、柔軟に使用できる @lru_cache デコレーターを使用することをお勧めしますが、キャッシュの数の制御に注意し、必要に応じて手動でクリーンアップしてください。
3) クラス コードを記述するときに、プロパティなどの複雑な操作の結果にアクセスする必要がある場合は、@cached_property デコレーターを使用します。

おすすめ

転載: blog.csdn.net/captain5339/article/details/131434063