それで、最近、メモ化について質問し、いくつかの素晴らしい回答を得ました。そして今、それを次のレベルに引き上げたいと思います。かなりグーグルした後、キーワード引数を取る関数をキャッシュできるメモ化デコレータのリファレンス実装を見つけることができませんでした。実際、それらのほとんどは単に*args
キャッシュルックアップのキーとして使用されていました。つまり、リストまたはdictを引数として受け入れる関数をメモ化する場合にも、キャッシュルックアップが機能しなくなります。
私の場合、関数の最初の引数はそれ自体が一意の識別子であり、キャッシュルックアップのdictキーとして使用するのに適していますが、キーワード引数を使用して同じキャッシュにアクセスできるようにする必要がありました。つまり、両方とも同じキャッシュ結果を返す必要がありますmy_func('unique_id', 10)
。my_func(foo=10, func_id='unique_id')
これを行うために必要なのは、「最初の引数に対応するキーワードがどれであれ、kwargsを検査する」というクリーンでパイソン的な言い方です。これは私が思いついたものです:
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('12345', 20)
またはFooBar(irrelevant=20, unique_id='12345')
を呼び出して、実際にFooBarの同じインスタンスを取得できます。次に、最初の引数に異なる名前で別のクラスを定義できます。これは、一般的な方法で機能するためです(つまり、デコレータは、これが機能するために、装飾しているクラスについて特定の情報を知る必要はありません)。
問題は、それは不敬虔な混乱だということです;-)
inspect.getcallargs
定義されたキーワードを指定した引数にマッピングするdictを返すため、これは機能します。そこで、偽の引数をいくつか指定してから、渡された最初の引数についてdictを調べます。
そのようなものが存在する場合でも、はるかに良いinspect.getcallargs
のは、キーワード引数のdictとしてではなく、引数のリストとして統合された両方の種類の引数を返すものに類似しています。これにより、次のようなことが可能になります。
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]
これに取り組むもう1つの方法は、ルックアップキャッシュキーとして提供されるdictをinspect.getcallargs
直接使用することですが、同じハッシュから同じ文字列を作成するための繰り返し可能な方法が必要になります。これは信頼できないと聞いています。 on(キーを並べ替えた後、自分で文字列を作成する必要があると思います)。
誰かがこれについて何か考えを持っていますか?キーワード引数を使用して関数を呼び出し、結果をキャッシュするのは間違っていますか?それとも非常に難しいですか?