2

私はメモ化のためにこのデコレータの変形を使用しています:

# note that this decorator ignores **kwargs
def memoize(obj):
    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        if args not in cache:
            cache[args] = obj(*args, **kwargs)
        return cache[args]
    return memoizer

argsと の両方に基づいてメモ化する合理的な方法はありkwargsますか?

4

5 に答える 5

2

パラメータを常にポジショナルとして、または常にキーワードとして使用している場合、Thorsten ソリューションは正常に機能します。ただし、パラメーターがどのように渡されるかに関係なく、パラメーターに同じ値を与える等しい呼び出しを検討したい場合は、より複雑なことを行う必要があります。

import inspect


def make_key_maker(func):
    args_spec = inspect.getargspec(func)

    def key_maker(*args, **kwargs):
        left_args = args_spec.args[len(args):]
        num_defaults = len(args_spec.defaults or ())
        defaults_names = args_spec.args[-num_defaults:]

        if not set(left_args).symmetric_difference(kwargs).issubset(defaults_names):
            # We got an error in the function call. Let's simply trigger it
            func(*args, **kwargs)

        start = 0
        key = []
        for arg, arg_name in zip(args, args_spec.args):
            key.append(arg)
            if arg_name in defaults_names:
                start += 1

        for left_arg in left_args:
            try:
                key.append(kwargs[left_arg])
            except KeyError:
                key.append(args_spec.defaults[start])

            # Increase index if we used a default, or if the argument was provided
            if left_arg in defaults_names:
                start += 1
        return tuple(key)

    return key_maker

上記の関数は、キーワード引数 (およびデフォルト) を位置指定にマップしようとし、結果のタプルをキーとして使用します。少しテストしましたが、ほとんどの場合、適切に動作するようです。ターゲット関数も**kwargs引数を使用すると失敗します。

>>> def my_function(a,b,c,d,e=True,f="something"): pass
... 
>>> key_maker = make_key_maker(my_function)
>>> 
>>> key_maker(1,2,3,4)
(1, 2, 3, 4, True, 'something')
>>> key_maker(1,2,3,4, e=True)               # same as before
(1, 2, 3, 4, True, 'something')
>>> key_maker(1,2,3,4, True)                 # same as before
(1, 2, 3, 4, True, 'something')
>>> key_maker(1,2,3,4, True, f="something")  # same as before
(1, 2, 3, 4, True, 'something')
>>> key_maker(1,2,3,4, True, "something")    # same as before
(1, 2, 3, 4, True, 'something')
>>> key_maker(1,2,3,d=4)                     # same as before
(1, 2, 3, 4, True, 'something')
>>> key_maker(1,2,3,d=4, f="something")      # same as before
(1, 2, 3, 4, True, 'something')
于 2013-01-31T10:54:19.503 に答える
1
import inspect
def memoize(obj):
    cache = obj.cache = {}
    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        kwargs.update(dict(zip(inspect.getargspec(obj).args, args)))
        key = tuple(kwargs.get(k, None) for k in inspect.getargspec(obj).args)
        if key not in cache:
            cache[key] = obj(**kwargs)
        return cache[key]
    return memoizer
于 2013-01-31T10:54:33.420 に答える
1

一般に、2 つの呼び出しが同じパラメーターの意味を持つと推測することはできません。呼び出しを検討する

func(foo=1)
func(1)
func(bar=1)

これらのどちらが (存在する場合) 同等であるかは、位置引数が foo と呼ばれるか bar と呼ばれるかによって異なります。引数が foo と呼ばれる場合、最初の呼び出しは 2 番目の呼び出しと一致します。ただし、位置パラメーターはまったく異なる名前を持つこともできます。 .

IOW、呼び出される関数を検討する必要がありますが、これは不可能な場合があります (たとえば、C で実装されているか、それ自体が *args、**kwargs のみを処理するラッパーである場合)。

リフレクション ルートに進みたい場合は、ndpu の応答などから始めるとよいでしょう。

于 2013-01-31T11:07:35.700 に答える
0

との両方からキーを作成するための良い方法を見つける必要がargsありkwargsます。多分これを試してみてください:

import functools
from collections import OrderedDict

# note that this decorator ignores **kwargs
def memoize(obj):
    def make_key(args, kwargs):
        ordered_kwargs = OrderedDict(kwargs)
        parameters = tuple([args, 
                            tuple(ordered_kwargs.keys()), 
                            tuple(ordered_kwargs.values())])
        return parameters
    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        key = make_key(args, kwargs)
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
            print "Not using cached result for key %s" % str(key)
        else:
            print "Using cached result for key %s" % str(key)
        return cache[key]
    return memoizer

@memoize
def calculate_sum(*args, **kwargs):
    return sum(args)

calculate_sum(4,7,9,2,flag=0)
calculate_sum(4,7,9,3)
calculate_sum(4,7,9,2,flag=1)
calculate_sum(4,7,9,2,flag=0)

それが機能することを示すために、いくつかの印刷ステートメントをに入れましmemoizerた。出力は次のとおりです。

Not using cached result for key ((4, 7, 9, 2), ('flag',), (0,))
Not using cached result for key ((4, 7, 9, 3), (), ())
Not using cached result for key ((4, 7, 9, 2), ('flag',), (1,))
Using cached result for key ((4, 7, 9, 2), ('flag',), (0,))

特に、kwargs(またはargs)として渡された値がハッシュ可能でない場合は、すべてのコーナーケースに取り組んだわけではないと確信しています。しかし、多分それは良い出発点として役立つことができます。

于 2013-01-31T09:44:56.453 に答える