8

明らかに、すばやく検索すると、Python のメモ化デコレーターの実装とフレーバーが 100 万個も見つかります。しかし、まだ見つけられていないフレーバーに興味があります。保存された値のキャッシュが固定容量になるようにしたいと思います。新しい要素が追加され、容量に達した場合、最も古い値が削除され、最新の値に置き換えられます。

私の懸念は、メモ化を使用して非常に多くの要素を保存すると、メモリ不足のためにプログラムがクラッシュすることです。(この懸念が実際にどれほど適切に配置されているかはわかりません。) キャッシュのサイズが固定されている場合、メモリ エラーは問題になりません。そして、私が取り組んでいる多くの問題は、プログラムが実行されるにつれて変化するため、最初にキャッシュされた値は、後でキャッシュされた値とは大きく異なって見えます (そして、後で再発する可能性ははるかに低くなります)。だから、古いものは新しいものに置き換えてほしい。

OrderedDictクラスと、それをサブクラス化して最大サイズを指定する方法を示す例を見つけました。通常のキャッシュではなく、それをキャッシュとして使用したいと思いますdict。問題は、 memoize デコレータmaxlenがデフォルトでと呼ばれるパラメータを取る必要があることNoneです。である場合None、キャッシュは無限であり、通常どおりに動作します。その他の値は、キャッシュのサイズとして使用されます。

私はそれが次のように動作することを望みます:

@memoize
def some_function(spam, eggs):
    # This would use the boundless cache.
    pass

@memoize(200)  # or @memoize(maxlen=200)
def some_function(spam, eggs):
    # This would use the bounded cache of size 200.
    pass

以下は私がこれまでに持っているコードですが、「ネイキッド」とパラメーターの両方を機能させながら、パラメーターをデコレーターに渡す方法がわかりません。

import collections
import functools

class BoundedOrderedDict(collections.OrderedDict):
    def __init__(self, *args, **kwds):
        self.maxlen = kwds.pop("maxlen", None)
        collections.OrderedDict.__init__(self, *args, **kwds)
        self._checklen()

    def __setitem__(self, key, value):
        collections.OrderedDict.__setitem__(self, key, value)
        self._checklen()

    def _checklen(self):
        if self.maxlen is not None:
            while len(self) > self.maxlen:
                self.popitem(last=False)

def memoize(function):
    cache = BoundedOrderedDict()  # I want this to take maxlen as an argument
    @functools.wraps(function)
    def memo_target(*args):
        lookup_value = args
        if lookup_value not in cache:
            cache[lookup_value] = function(*args)
        return cache[lookup_value]
    return memo_target

@memoize
def fib(n):
    if n < 2: return 1
    return fib(n-1) + fib(n-2)

if __name__ == '__main__':
    x = fib(50)
    print(x)

編集:ベンの提案を使用して、次のデコレータを作成しました。これは、私が想像したとおりに機能すると信じています。これらの装飾された関数を で使用できることは私にとって重要でmultiprocessingあり、これは過去の問題でした。しかし、このコードの簡単なテストでは、ジョブをスレッドのプールにファームアウトした場合でも、正しく動作するように見えました。

def memoize(func=None, maxlen=None):
    if func:
        cache = BoundedOrderedDict(maxlen=maxlen)
        @functools.wraps(func)
        def memo_target(*args):
            lookup_value = args
            if lookup_value not in cache:
                cache[lookup_value] = func(*args)
            return cache[lookup_value]
        return memo_target
    else:
        def memoize_factory(func):
            return memoize(func, maxlen=maxlen)
        return memoize_factory
4

3 に答える 3

4
@memoize
def some_function(spam, eggs):
    # This would use the boundless cache.
    pass

Herememoizeは、単一の関数引数で呼び出され、関数を返す関数として使用されます。memoizeデコレータです。

@memoize(200)  # or @memoize(maxlen=200)
def some_function(spam, eggs):
    # This would use the bounded cache of size 200.
    pass

ここでmemoizeは、単一の整数引数で呼び出されて関数を返す関数として使用され、返された関数自体がデコレータとして使用されます。つまり、単一の関数引数で呼び出されて関数を返します。デコレーターファクトリーmemoizeです。

したがって、これら 2 つを統合するには、醜いコードを作成する必要があります。私がおそらくそれを行う方法は、次のようにすることですmemoize

def memoize(func=None, maxlen=None):
    if func:
        # act as decorator
    else:
        # act as decorator factory

このように、パラメーターを渡したい場合は、常にキーワード引数として渡し、func(位置パラメーターである必要があります)未設定のままにします。すべてをデフォルトにしたい場合は、魔法のように直接デコレーターとして機能します。これは@memoize(200)、エラーが発生することを意味します。代わりに、型チェックを行って呼び出し可能かどうかを確認することfuncで、これを回避できます。これは実際にはうまく機能するはずですが、実際にはあまり「pythonic」ではありません。

memoize別の方法として、 となどの 2 つの異なるデコレータを用意することもできますbounded_memoize。unboundedはset toでmemoize呼び出すだけで簡単な実装ができるため、実装やメンテナンスに費用はかかりません。bounded_memoizemaxlenNone

通常、経験則として、関数をマングリングして、接線のみで関連する 2 つの機能セットを実装することは避けようとします。特に、これらの署名が異なる場合はそうです。しかし、この場合、デコレータの使用は自然になります (requiring@memoize()は、理論的な観点からは一貫性がありますが、かなりエラーが発生しやすくなります)。これを一度実装して何度も使用することになると思われます。使用時の可読性は、おそらくより重要な関心事です。

于 2012-02-22T05:13:15.893 に答える
0

引数 ( の最大長) を取り、適切なサイズの でBoundedOrderedDict関数をメモ化するデコレータを返すデコレータを書きたいとします。BoundedOrderedDict

def boundedMemoize(maxCacheLen):
    def memoize(function):
        cache = BoundedOrderedDict(maxlen = maxCacheLen)
        def memo_target(*args):
            lookup_value = args
            if lookup_value not in cache:
                cache[lookup_value] = function(*args)
            return cache[lookup_value]
        return memo_target
    return memoize

次のように使用できます。

@boundedMemoize(100)
def fib(n):
    if n < 2: return 1
    return fib(n - 1) + fib(n - 2)

編集:おっと、質問の一部を見逃しました。デコレータへの maxlen 引数をオプションにしたい場合は、次のようにすることができます:

def boundedMemoize(arg):
    if callable(arg):
        cache = BoundedOrderedDict()
        @functools.wraps(arg)
        def memo_target(*args):
            lookup_value = args
            if lookup_value not in cache:
                cache[lookup_value] = arg(*args)
            return cache[lookup_value]
        return memo_target

    if isinstance(arg, int):
        def memoize(function):
            cache = BoundedOrderedDict(maxlen = arg)
            @functools.wraps(function)
            def memo_target(*args):
                lookup_value = args
                if lookup_value not in cache:
                    cache[lookup_value] = function(*args)
                return cache[lookup_value]
            return memo_target
        return memoize
于 2012-02-22T05:21:43.337 に答える
-2

http://www.python.org/dev/peps/pep-0318/から

現在の構文では、デコレータ宣言でデコレータを返す関数を呼び出すこともできます。

@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
    pass

これは次と同等です。

func = decomaker(argA, argB, ...)(func)

また、これに OrderedDict を使用するかどうかはわかりません。実装が非常に簡単なリング バッファーを使用します。

于 2012-02-22T05:13:07.557 に答える