あなたが目指している機能が「私の1つのインスタンスがスタック上でアクティブである」ことをマークするために非常に特別なことをしない限り(IOW:機能が手付かずで手に負えず、おそらくあなたのこの独特の必要性を認識できない場合) 、スタックをフレームごとに上に移動する代わりに考えられる方法はありません。これは、スタックの一番上 (関数がそこにない) または目的の関数のスタック フレームに到達するまでです。質問に対するいくつかのコメントが示すように、これを最適化するために努力する価値があるかどうかは非常に疑わしいです。しかし、議論のためにそれが価値があると仮定すると...:
編集:元の回答(OPによる)には多くの欠陥がありましたが、一部は修正されたため、現在の状況と特定の側面が重要な理由を反映するように編集しています。
まず、デコレータでtry
/ except
、またはを使用することが重要です。これにより、通常の関数だけでなく、監視対象の関数からのすべての終了が適切に考慮されます (OP 自身の回答の元のバージョンが行ったように)。with
__name__
第二に、すべてのデコレーターは、デコレートされた関数をそのまま維持する必要があります__doc__
-それfunctools.wraps
が目的です(他の方法もありますが、wraps
最も簡単になります)。
3 番目に、最初のポイントと同じくらい重要な aset
は、OP によって最初に選択されたデータ構造でしたが、間違った選択です。関数はスタックに複数回存在する可能性があります (直接または間接再帰)。各アイテムが「何回」存在するかを追跡するセットのような構造である「マルチセット」(「バッグ」とも呼ばれる) が明らかに必要です。Python では、マルチセットの自然な実装は、キーをカウントにマッピングする dictcollections.defaultdict(int)
です。
第 4 に、一般的なアプローチはスレッドセーフであるべきです (それが簡単に達成できる場合は、少なくとも;-)。幸いなことthreading.local
に、該当する場合は簡単になります。ここでは、確かにそうする必要があります (各スタックには独自の呼び出しスレッドがあります)。
第5に、いくつかのコメントでブローチされた興味深い問題です(いくつかの回答で提供されたデコレーターが他のデコレーターとどれほどひどく遊んでいるかに注意してください:監視デコレーターは最後(最も外側)のものでなければならないように見えます。関数オブジェクト自体を監視辞書のキーとして使用するという、自然ではあるが残念な選択です。
別のキーを選択することでこれを解決することを提案します。デコレーターに (文字列などの)identifier
引数を (与えられたスレッドごとに) 一意にする必要があり、その識別子を監視辞書へのキーとして使用するようにします。スタックをチェックするコードは、もちろん識別子を認識し、それを使用する必要があります。
装飾時に、デコレータは (別のセットを使用して) 一意性プロパティをチェックできます。識別子は、デフォルトで関数名のままにすることができます (したがって、同じ名前空間で同名関数を監視する柔軟性を維持するために明示的に必要とされるだけです)。いくつかの監視対象機能が監視目的で「同じ」と見なされる場合、一意性プロパティを明示的に放棄することができます (これは、特定のdef
ステートメントは、プログラマーが監視目的で「同じ関数」と見なしたいいくつかの関数オブジェクトを作成するために、わずかに異なるコンテキストで複数回実行されることを意図しています)。最後に、それ以上の装飾が不可能であることが知られているまれなケースでは、オプションで「識別子としての関数オブジェクト」に戻すことができるはずです (そのような場合、一意性を保証する最も簡単な方法になる可能性があるため)。
したがって、これらの多くの考慮事項をまとめるとthreadlocal_var
、次のようなものを作成できます (もちろん、ツールボックス モジュールに既に含まれている可能性があるユーティリティ関数を含みます;-)。
import collections
import functools
import threading
threadlocal = threading.local()
def threadlocal_var(varname, factory, *a, **k):
v = getattr(threadlocal, varname, None)
if v is None:
v = factory(*a, **k)
setattr(threadlocal, varname, v)
return v
def monitoring(identifier=None, unique=True, use_function=False):
def inner(f):
assert (not use_function) or (identifier is None)
if identifier is None:
if use_function:
identifier = f
else:
identifier = f.__name__
if unique:
monitored = threadlocal_var('uniques', set)
if identifier in monitored:
raise ValueError('Duplicate monitoring identifier %r' % identifier)
monitored.add(identifier)
counts = threadlocal_var('counts', collections.defaultdict, int)
@functools.wraps(f)
def wrapper(*a, **k):
counts[identifier] += 1
try:
return f(*a, **k)
finally:
counts[identifier] -= 1
return wrapper
return inner
私はこのコードをテストしていないので、誤字脱字などがあるかもしれませんが、上で説明したすべての重要な技術的ポイントがカバーされていることを願って提供します。
それはすべてそれだけの価値がありますか?前に説明したように、おそらくそうではありません。しかし、私は「やる価値があるなら、正しくやる価値がある」という考え方に沿っていると思います;-)。