11

それで、最近、メモ化について質問し、いくつかの素晴らしい回答を得ました。そして今、それを次のレベルに引き上げたいと思います。かなりグーグルした後、キーワード引数を取る関数をキャッシュできるメモ化デコレータのリファレンス実装を見つけることができませんでした。実際、それらのほとんどは単に*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(キーを並べ替えた後、自分で文字列を作成する必要があると思います)。

誰かがこれについて何か考えを持っていますか?キーワード引数を使用して関数を呼び出し、結果をキャッシュするのは間違っていますか?それとも非常に難しいですか?

4

3 に答える 3

6

次のようなものをお勧めします。

import inspect

class key_memoized(object):
    def __init__(self, func):
       self.func = func
       self.cache = {}

    def __call__(self, *args, **kwargs):
        key = self.key(args, kwargs)
        if key not in self.cache:
            self.cache[key] = self.func(*args, **kwargs)
        return self.cache[key]

    def normalize_args(self, args, kwargs):
        spec = inspect.getargs(self.func.__code__).args
        return dict(kwargs.items() + zip(spec, args))

    def key(self, args, kwargs):
        a = self.normalize_args(args, kwargs)
        return tuple(sorted(a.items()))

例:

@key_memoized
def foo(bar, baz, spam):
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam)
    return bar + baz + spam

print foo(1, 2, 3)
print foo(1, 2, spam=3)         #memoized
print foo(spam=3, baz=2, bar=1) #memoized

key_memoizedメソッドを拡張およびオーバーライドしkey()て、より具体的なメモ化戦略を提供することもできることに注意してください。たとえば、いくつかの引数を無視します。

class memoize_by_bar(key_memoized):
    def key(self, args, kwargs):
        return self.normalize_args(args, kwargs)['bar']

@memoize_by_bar
def foo(bar, baz, spam):
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam)
    return bar

print foo('x', 'ignore1', 'ignore2')
print foo('x', 'ignore3', 'ignore4')
于 2012-06-06T20:06:55.813 に答える
3

lru_cache を試してください:

@functools.lru_cache(maxsize=128, typed=False)

最大 maxsize の最新の呼び出しを保存する memoizing callable で関数をラップするデコレーター。高価な関数または I/O バウンド関数が同じ引数で定期的に呼び出される場合、時間を節約できます。

lru_cache は Python 3.2 で追加されましたが、2.x にバックポートできます

于 2012-06-06T18:47:42.917 に答える
0

私のパッケージを見ることができます: https://github.com/Yiling-J/cacheme、実際にはすべてのargs/kwargsにコンテナを使用しています:

@cacheme(key=lambda c: 'cat:{name}'.format(name=c.cat.name))
def get_cat(self, cat):
    return some_function(cat)
于 2020-04-26T15:07:37.580 に答える