最近私はメモ作成に関する質問をしていくつかの大きな回答を得ましたが、今度はそれを次のレベルに引き上げたいと思います。かなりのグーグルで、私はキーワード引数を取った関数をキャッシュすることができたmemoizeデコレータのリファレンス実装を見つけることができませんでした。実際には、ほとんどの人がキャッシュルックアップのキーとして単に*args
を使用しました。つまり、リストやdictsを引数として受け入れる関数をメモしたいと思っていた場合にも壊れます。Pythonでmemoizeデコレータのキーワード引数をサポートするためのpyononicな方法はありますか?
私の場合、関数の最初の引数は、キャッシュルックアップのdictキーとして使用するのに適した一意の識別子ですが、キーワード引数を使用して同じキャッシュにアクセスする必要がありました。つまり、my_func('unique_id', 10)
とmy_func(foo=10, func_id='unique_id')
の両方が同じキャッシュ結果を返すはずです。
これを実行するには、「最初の引数に対応するキーワードのどれかを調べる」というクリーンで非凡な方法が必要です。これは私が思いついたものです:
class memoize(object):
def __init__(self, cls):
if type(cls) is FunctionType:
# Let's just pretend that the function you gave us is a class.
cls.instances = {}
cls.__init__ = cls
self.cls = cls
self.__dict__.update(cls.__dict__)
def __call__(self, *args, **kwargs):
"""Return a cached instance of the appropriate class if it exists."""
# This is some dark magic we're using here, but it's how we discover
# that the first argument to Photograph.__init__ is 'filename', but the
# first argument to Camera.__init__ is 'camera_id' in a general way.
delta = 2 if type(self.cls) is FunctionType else 1
first_keyword_arg = [k
for k, v in inspect.getcallargs(
self.cls.__init__,
'self',
'first argument',
*['subsequent args'] * (len(args) + len(kwargs) - delta)).items()
if v == 'first argument'][0]
key = kwargs.get(first_keyword_arg) or args[0]
print key
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args, **kwargs)
return self.cls.instances[key]
これは実際に動作することです。あなたはこのように飾る場合などは、:あなたのコードから次に
@memoize
class FooBar:
instances = {}
def __init__(self, unique_id, irrelevant=None):
print id(self)
あなたが実際にFooBarのの同じインスタンスを取得FooBar('12345', 20)
またはFooBar(irrelevant=20, unique_id='12345')
とのいずれかを呼び出すことができます。最初の引数とは異なる名前の別のクラスを定義することができます。一般的な方法で動作します(つまり、デコレータは、これを動作させるために装飾するクラスについて何も知る必要はありません)。
は、それは不信心混乱;-) inspect.getcallargs
は、あなたがそれを指定する引数に定義されたキーワードをマッピングする辞書を返すので、それが動作
ですので、私はいくつかの偽の引数を供給し、その後のための辞書を調べ渡された最初の引数。
このようなことが存在するのであれば、両方の種類の引数をキーワード引数の代わりに引数のリストとして統一したinspect.getcallargs
と類似しています。つまり、このようなことが可能になる:
def __call__(self, *args, **kwargs):
key = inspect.getcallargsaslist(self.cls.__init__, None, *args, **kwargs)[1]
if key not in self.cls.instances:
self.cls.instances[key] = self.cls(*args, **kwargs)
return self.cls.instances[key]
私が直接参照キャッシュキーとしてinspect.getcallargs
が提供する辞書を使用することになり、これを取り組んで見ることができ、他の方法、それから、同一の文字列を作るために繰り返し可能な方法を必要とします私が聞いたことがあるものであるとは限りません(私はキーをソートした後に自分自身で文字列を構築しなければならないと思います)。
誰もがこれに関する考えを持っていますか?キーワード引数を持つ関数を呼び出して結果をキャッシュするのは間違っていますか?それとも非常に難しい?
私はこれの外観が気に入っていますが、 'key'が' tuple(a.items()) 'を返す部分について心配しています。それは別個であるが同一のdictsのために同じ順序でキーをソートすることが保証されていますか?私はdictが順序付けされておらず、同じ入力が与えられた場合に繰り返し可能な文字列を生成する 'str({1:2,3:4})'のようなものに頼っていると聞いています。 – robru
'inspect.getargspec(func).args [0]'は私が尋ねた特定の質問(最初の引数の名前を見つける方法)に対する正確な答えですが、これを拡張してより一般的な解決策。私は一度それを巧みにする時間があると、後で結果を投稿します。 – robru
@Robru:dictソートについての良い点。 'tuple(ソート済み(a.items()))'に変更されました(別のオプションは 'frozenset(a.items())')。 – georg