@memoize
最近、StackOverflow コミュニティは、関数だけでなくメソッドやクラスも一般的な方法で、つまり、どのような種類のものを装飾するかを事前に知らなくても装飾できる、かなり簡潔なデコレーターの開発を支援してくれました。
私が遭遇した問題の 1 つは、クラスを@memoize
で装飾し、そのメソッドの 1 つを で装飾しようとすると@staticmethod
、期待どおりに動作しない、つまり、まったく呼び出すことができないということClassName.thestaticmethod()
です。私が思いついた元のソリューションは次のようになりました。
def memoize(obj):
"""General-purpose cache for classes, methods, and functions."""
cache = obj.cache = {}
def memoizer(*args, **kwargs):
"""Do cache lookups and populate the cache in the case of misses."""
key = args[0] if len(args) is 1 else args
if key not in cache:
cache[key] = obj(*args, **kwargs)
return cache[key]
# Make the memoizer func masquerade as the object we are memoizing.
# This makes class attributes and static methods behave as expected.
for k, v in obj.__dict__.items():
memoizer.__dict__[k] = v.__func__ if type(v) is staticmethod else v
return memoizer
functools.wraps
しかし、その後、よりクリーンで完全な方法でデコレータ関数を装飾された関数として偽装することを目的としたについて学び、実際に次のように採用しました。
def memoize(obj):
"""General-purpose cache for class instantiations, methods, and functions."""
cache = obj.cache = {}
@functools.wraps(obj)
def memoizer(*args, **kwargs):
"""Do cache lookups and populate the cache in the case of misses."""
key = args[0] if len(args) is 1 else args
if key not in cache:
cache[key] = obj(*args, **kwargs)
return cache[key]
return memoizer
これは非常に良さそうに見えますが、 sfunctools.wraps
も s もまったくサポートしていません。たとえば、次のようなことを試した場合:staticmethod
classmethod
@memoize
class Flub:
def __init__(self, foo):
"""It is an error to have more than one instance per foo."""
self.foo = foo
@staticmethod
def do_for_all():
"""Have some effect on all instances of Flub."""
for flub in Flub.cache.values():
print flub.foo
Flub('alpha') is Flub('alpha') #=> True
Flub('beta') is Flub('beta') #=> True
Flub.do_for_all() #=> 'alpha'
# 'beta'
@memoize
これは、リストされた Iの最初の実装では機能しますがTypeError: 'staticmethod' object is not callable
、2 番目の実装では発生します。
私は本当に、functools.wraps
その醜さを取り戻すことなく、使用するだけでこれを解決したかったので、実際に純粋な Python で__dict__
自分自身を再実装しました。これは次のようになりました。staticmethod
class staticmethod(object):
"""Make @staticmethods play nice with @memoize."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
"""Provide the expected behavior inside memoized classes."""
return self.func(*args, **kwargs)
def __get__(self, obj, objtype=None):
"""Re-implement the standard behavior for non-memoized classes."""
return self.func
そして、これは、私が知る限り、上記の 2 番目の@memoize
実装と完全に連携して機能します。
それで、私の質問は次のとおりです。標準のビルトインがそれ自体で適切に動作しないのはなぜですかstaticmethod
、および/またはfunctools.wraps
この状況を予測して解決しないのはなぜですか?
これは Python のバグですか? またはでfunctools.wraps
?
ビルトインをオーバーライドする際の注意事項は何staticmethod
ですか? 私が言うように、今は問題なく動作しているようですが、私の実装と組み込みの実装の間に隠れた非互換性があり、後で爆発する可能性があるのではないかと心配しています.
ありがとう。
明確にするために編集:私のアプリケーションには、高価な検索を行い、頻繁に呼び出される関数があるため、メモしました。それは非常に簡単です。それに加えて、ファイルを表す多くのクラスがあり、ファイル システム内の同じファイルを表す複数のインスタンスを持つと、通常、一貫性のない状態になるため、ファイル名ごとに 1 つのインスタンスのみを適用することが重要です。デコレーターをこの目的に適合させ@memoize
、従来のメモライザーとしての機能を維持することは本質的に簡単です。
の 3 つの異なる用途の実際の例を次に示し@memoize
ます。