Python で変数をスタック/コンテキストに配置する方法
-
05-07-2019 - |
質問
本質的には、ブロックが終了するまで、スタック上のその部分の下にあるすべての呼び出しでアクセスできる変数をスタックに置きたいと考えています。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 されます。
解決
私は先に進み、あなたが望むことをするだけの何かを作りました。デコレータとコンテキストマネージャの両方として使用できます。
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))
他のヒント
この質問は 2 つの質問のようです
- a) データベース接続の共有
- b) キャッシュ/メモ化
b) あなたは自分で答えました
a) なぜスタックに置く必要があるのか理解できないようです。これらのいずれかを行うことができます
- あなたはクラスを使用することができ、接続はそれの属性になる可能性があります
- すべての機能を飾ることができ、中央の場所から接続を取得することができます
- 各関数は、グローバル接続法を明示的に使用できます
- 接続を作成してそれを渡すか、コンテキストオブジェクトを作成してコンテキストを渡すことができます。接続はコンテキストの一部になります
などなど
ゲッター関数でラップされたグローバル変数を使用できます:
def getConnection():
global connection
if connection:
return connection
connection=createConnection()
return connection
&quot;リクエストを取得し、データベース接続を開きます。...データベース接続を閉じます。&quot;
これがオブジェクトの目的です。接続オブジェクトを作成し、他のオブジェクトに渡し、完了したら閉じます。グローバルは適切ではありません。単純に、値をパラメーターとして、作業を実行している他のオブジェクトに渡します。
&quot;各レポートは複数の部分で構成され、各部分は異なる計算に依存する場合があり、異なる部分が同じ計算に部分的に依存する場合があります。それらをキャッシュする必要があります。
これがオブジェクトの目的です。有用な計算結果を含む辞書を作成し、レポートパーツからレポートパーツに渡します。
「スタック変数」、「静的スレッドローカル」を台無しにする必要はありません。またはそのようなもの。 通常の変数引数を通常のメソッド関数に渡すだけです。あなたはもっと幸せになります。
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
任意の数の計算を定義し、それらをすべて単一のコンテナオブジェクトに折り畳むことができます。
必要に応じて、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 __
メソッドが呼び出されたことを保証できます。