def memo_square(a, cache={}): if a not in cache: cache[a] = a*a return cache[a]
レセプションはあまり知られていないので、カットの下で、それがどのように機能し、何のためであるかを分析します。
最初に、それがどのようにそしてなぜ働くか。
memo_square
(他の関数と同様に)関数クラスのオブジェクトであり、属性の中でも、オブジェクトの作成時に
memo_square.__defaults__
タプルが入力されます。 まず、関数ヘッダーに示されているように、空の辞書が含まれています。
>>> memo_square.__defaults__ ({},)
__defaults__
は通常のタプルであり、その要素を変更することはできません。 確かに、デフォルト値のセット全体を一度に置き換えることができますが、異なるタプルでのみ:
>>> def test(a=1, b=2): ... print(a, b) ... >>> test.__defaults__ (1, 2) >>> test() 1 2 >>> test.__defaults__ = (', ', '') >>> test() , >>> test.__defaults__[1] = '' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> test.__defaults__ = {0: ', ', 1: ''} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __defaults__ must be set to a tuple object
Soryan、この記事はPicabaには届きません。 さて、大丈夫、これは重要ではありません。 重要なことは、非常にトリッキーなコードを除いて、
func.__defaults__
がプログラムのすべての要素で一度作成されることです。 タプルとその要素は、関数呼び出しごとに再作成されるのではなく、関数が存在する限り使用されます。 しかし、変更するには、要素自体が可変である場合、誰もそれらを禁止しません。 このような要素を使用できないことは、Pythonで自分自身を撃つ最も一般的な方法の1つです 。 しかし、実際に関数呼び出し間で値を保存することは非常に便利です。 数回呼び出した後、
memo_square.__defaults__
は次のようになります。
>>> memo_square(2) 4 >>> memo_square.__defaults__ ({2: 4},) >>> memo_square(5) 25 >>> memo_square.__defaults__ ({2: 4, 5: 25},) >>> memo_square(2) 4 >>> memo_square.__defaults__ ({2: 4, 5: 25},)
同じ値に対して関数がすでに呼び出されている場合、値が計算され、それに応じてキャッシュは補充されません。 正方形の場合、利点は小さいです(厳密に言えば、正方形の場合、辞書での検索は2つの数字を乗算するよりも高価であるため、利益はマイナスです)が、メモ化/キャッシングは実際の高価な機能に役立ちます。 もちろん、Pythonで複数の方法で提供できます。 代替手段は次のとおりです。
- @ functools.lru_cache 。 最後の関数呼び出しを記憶するfunctoolsモジュールのデコレーター。 信頼性が高くシンプルですが、関数のすべてのパラメーターをキーとして使用するため、ハッシュ可能性が必要であり、形式的に異なる2つのパラメーター値が同等であることを認識できません。 最初の要件では、セットの関数についてはすべてが明確になります。たとえば、忘れることができます。 さて、または呼び出すときに、それらをfrozensetに変換します。 たとえば、2番目の関数については、SQLデータベースへの接続と数値を入力として受け入れ、この数値に関連付けられたデータを使用していくつかの操作を実行する関数があります。 プログラムの動作中に接続が切断されて再確立される可能性があり、lru_cacheキャッシュがクラッシュします。 しかし、彼は限られた数の呼び出ししかキャッシュできず(メモリリークを回避)、十分に文書化されています。
- 関数の外部キャッシュ:
def square(a): return a**a cache = {} for x in values: if x not in cache: cache[x] = x**x print cache[x]
意味は同じですが、もっと面倒です。 さらに、キャッシュ変数は、関数の外部から見ることができますが、メモすること以外には使用されません。 デフォルト引数を使用したメモ化中のキャッシュは、func.__defaults__
を介してのみ外部からアクセスできますが、これは誤ってアクセスするのは非常に困難です。 - 本格的なオブジェクトをキャッシュでフラッシュし、その関数をメソッドにします。 アーキテクチャとテスト容易性の点で優れており、任意に複雑なキャッシングロジックを維持できますが、オブジェクトコードの定型文によりさらに面倒です。 さらに、複数のメモ可能な関数がある場合、何を継承し、何から継承するかは明確ではありません。
このメモ化の方法が失う主なことは、あまり慣用的ではないということです。 個人的に、私がこの決定に初めて出会ったとき、私はここで何が起こっているのか、そしてその理由について数分間考えました。 一方、これらの数分で、Python関数とその引数がどのように機能するかを少し理解し始めました。 したがって、デフォルトの引数を使用しない場合(メモ化または名前解決の高速化など )でも、この手法を知っていると、栄養士にとって役立ちます。