3

本質的に、ブロックが終了するまで、スタック上のその部分の下のすべての呼び出しによって到達可能な変数をスタックに配置したいと考えています。Java では、メソッドからアクセスできるサポート メソッドを備えた静的スレッド ローカルを使用してこれを解決します。

典型的な例: リクエストを受け取り、データベース接続を開きます。リクエストが完了するまで、すべてのコードでこのデータベース接続を使用する必要があります。要求を終了して閉じた後、データベース接続を閉じます。

これが必要なのは、レポートジェネレーターです。各レポートは複数の部分で構成され、各部分は異なる計算に依存する場合があり、異なる部分が同じ計算に部分的に依存する場合もあります。重い計算を繰り返したくないので、それらをキャッシュする必要があります。私の考えは、メソッドをキャッシュ デコレータで装飾することです。キャッシュは、メソッド名とモジュールに基づいて id を作成し、その引数は、これがスタック変数で既に計算されているかどうかを調べ、そうでない場合はメソッドを実行します。

現在の実装を示すことで明確にしようと思います。私がやりたいのは、計算を実装するためのコードを単純化することです。

まず、MathContext と呼ぶ中央キャッシュ アクセス オブジェクトがあります。

class MathContext(object):
    def __init__(self, fn): 
        self.fn = fn
        self.cache = dict()
    def get(self, calc_config):
        id = create_id(calc_config)
        if id not in self.cache:
            self.cache[id] = calc_config.exec(self)
        return self.cache[id]

fn 引数は、コンテキストが関連して作成されるファイル名であり、そこからデータを読み取って計算することができます。

次に、Calculation クラスがあります。

 class CalcBase(object):
     def exec(self, math_context):
         raise NotImplementedError

そして、これはばかげたフィボナッチの例です。メソッドは実際には再帰的ではなく、代わりに大量のデータセットで機能しますが、他の計算にどのように依存するかを示すために機能します。

class Fibonacci(CalcBase):
    def __init__(self, n): self.n = n
    def exec(self, math_context):
        if self.n < 2: return 1
        a = math_context.get(Fibonacci(self.n-1))
        b = math_context.get(Fibonacci(self.n-2))
        return a+b

代わりにフィボナッチにしたいのは、装飾されたメソッドです。

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

math_context の例では、math_context が範囲外になると、キャッシュされた値もすべて範囲外になります。デコレータにも同じことが必要です。すなわち。ポイント X では、@cache によってキャッシュされたものはすべて参照解除されて gced されます。

4

4 に答える 4

6

私は先に進み、あなたが望むことをするかもしれない何かを作りました. デコレータとコンテキスト マネージャの両方として使用できます。

from __future__ import with_statement
try:
    import cPickle as pickle
except ImportError:
    import pickle


class cached(object):
    """Decorator/context manager for caching function call results.
    All results are cached in one dictionary that is shared by all cached
    functions.

    To use this as a decorator:
        @cached
        def function(...):
            ...

    The results returned by a decorated function are not cleared from the
    cache until decorated_function.clear_my_cache() or cached.clear_cache()
    is called

    To use this as a context manager:

        with cached(function) as function:
            ...
            function(...)
            ...

    The function's return values will be cleared from the cache when the
    with block ends

    To clear all cached results, call the cached.clear_cache() class method
    """

    _CACHE = {}

    def __init__(self, fn):
        self._fn = fn

    def __call__(self, *args, **kwds):
        key = self._cache_key(*args, **kwds)
        function_cache = self._CACHE.setdefault(self._fn, {})
        try:
            return function_cache[key]
        except KeyError:
            function_cache[key] = result = self._fn(*args, **kwds)
            return result

    def clear_my_cache(self):
        """Clear the cache for a decorated function
        """
        try:
            del self._CACHE[self._fn]
        except KeyError:
            pass # no cached results

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.clear_my_cache()

    def _cache_key(self, *args, **kwds):
        """Create a cache key for the given positional and keyword
        arguments. pickle.dumps() is used because there could be
        unhashable objects in the arguments, but passing them to 
        pickle.dumps() will result in a string, which is always hashable.

        I used this to make the cached class as generic as possible. Depending
        on your requirements, other key generating techniques may be more
        efficient
        """
        return pickle.dumps((args, sorted(kwds.items())), pickle.HIGHEST_PROTOCOL)

    @classmethod
    def clear_cache(cls):
        """Clear everything from all functions from the cache
        """
        cls._CACHE = {}


if __name__ == '__main__':
    # used as decorator
    @cached
    def fibonacci(n):
        print "calculating fibonacci(%d)" % n
        if n == 0:
            return 0
        if n == 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)

    for n in xrange(10):
        print 'fibonacci(%d) = %d' % (n, fibonacci(n))


    def lucas(n):
        print "calculating lucas(%d)" % n
        if n == 0:
            return 2
        if n == 1:
            return 1
        return lucas(n - 1) + lucas(n - 2)

    # used as context manager
    with cached(lucas) as lucas:
        for i in xrange(10):
            print 'lucas(%d) = %d' % (i, lucas(i))

    for n in xrange(9, -1, -1):
        print 'fibonacci(%d) = %d' % (n, fibonacci(n))

    cached.clear_cache()

    for n in xrange(9, -1, -1):
        print 'fibonacci(%d) = %d' % (n, fibonacci(n))
于 2009-06-16T18:43:15.380 に答える
2

この質問は2つの質問のようです

  • a) データベース接続の共有
  • b) キャッシング/メモ化

b) 自分で答えた

a) なぜそれをスタックに置く必要があるのか​​ 理解できないのですか? あなたはこれらのいずれかを行うことができます

  1. クラスを使用でき、接続はその属性である可能性があります
  2. すべての機能を装飾して、中央の場所から接続できるようにすることができます
  3. 各関数は明示的にグローバル接続メソッドを使用できます
  4. 接続を作成してそれを渡すか、コンテキスト オブジェクトを作成してコンテキストを渡すことができます。接続はコンテキストの一部にすることができます

などなど

于 2009-06-16T14:35:58.643 に答える
0

ゲッター関数でラップされたグローバル変数を使用できます。

def getConnection():
    global connection
    if connection:
        return connection
    connection=createConnection()
    return connection
于 2009-06-16T14:27:36.803 に答える
0

「リクエストを受け取り、データベース接続を開きます....データベース接続を閉じます。」

これがオブジェクトの目的です。接続オブジェクトを作成し、それを他のオブジェクトに渡し、完了したら閉じます。グローバルは適切ではありません。作業を行っている他のオブジェクトに値をパラメーターとして渡すだけです。

「各レポートは複数の部分で構成されており、各部分は異なる計算に依存している可能性があり、異なる部分が同じ計算に部分的に依存している場合もあります....それらをキャッシュする必要があります」

これがオブジェクトの目的です。有用な計算結果でディクショナリを作成し、それをレポート パーツからレポート パーツに渡します。

「スタック変数」、「静的スレッドローカル」などをいじる必要はありません。通常の可変引数を通常のメソッド関数に渡すだけです。あなたはもっと幸せになるでしょう。


class MemoizedCalculation( object ):
    pass

class Fibonacci( MemoizedCalculation ):
    def __init__( self ):
       self.cache= { 0: 1, 1: 1 }
    def __call__( self, arg ):
       if arg not in self.cache:
           self.cache[arg]= self(arg-1) + self(arg-2)
       return self.cache[arg]

class MathContext( object ):
    def __init__( self ):
        self.fibonacci = Fibonacci()

こんな感じで使えます

>>> mc= MathContext()
>>> mc.fibonacci( 4 )
5

任意の数の計算を定義し、それらすべてを 1 つのコンテナー オブジェクトに折りたたむことができます。

必要に応じて、MathContext を正式な Context Manager にして、withステートメントと連携させることができます。これら 2 つのメソッドを MathContext に追加します。

def __enter__( self ):
    print "Initialize"
    return self
def __exit__( self, type_, value, traceback ):
    print "Release"

次に、これを行うことができます。

with  MathContext() as mc:
    print mc.fibonacci( 4 )

withステートメントの最後で、__exit__メソッドが呼び出されたことを保証できます。

于 2009-06-16T14:40:44.843 に答える