5

デバッグの場合、特定の関数がコール スタックの上位にあるかどうかを確認できると便利なことがよくあります。たとえば、特定の関数から呼び出されたときにだけデバッグ コードを実行したいことがよくあります。

1 つの解決策は、上位のすべてのスタック エントリを調べることですが、これがスタックの奥深くにあり、繰り返し呼び出される関数内にある場合、過剰なオーバーヘッドにつながります。問題は、特定の関数がコール スタックの上位にあるかどうかを合理的に効率的に判断できる方法を見つけることです。

似ている

4

2 に答える 2

14

あなたが目指している機能が「私の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

私はこのコードをテストしていないので、誤字脱字などがあるかもしれませんが、上で説明したすべての重要な技術的ポイントがカバーされていることを願って提供します。

それはすべてそれだけの価値がありますか?前に説明したように、おそらくそうではありません。しかし、私は「やる価値があるなら、正しくやる価値がある」という考え方に沿っていると思います;-)。

于 2009-09-10T05:16:11.000 に答える
1

私はこのアプローチがあまり好きではありませんが、これはあなたがしていたことの修正版です:

from collections import defaultdict
import threading
functions_on_stack = threading.local()

def record_function_on_stack(f):
    def wrapped(*args, **kwargs):
        if not getattr(functions_on_stack, "stacks", None):
            functions_on_stack.stacks = defaultdict(int)
        functions_on_stack.stacks[wrapped] += 1

        try:
            result = f(*args, **kwargs)
        finally:
            functions_on_stack.stacks[wrapped] -= 1
            if functions_on_stack.stacks[wrapped] == 0:
                del functions_on_stack.stacks[wrapped]
        return result

    wrapped.orig_func = f
    return wrapped

def function_is_on_stack(f):
    return f in functions_on_stack.stacks

def nested():
    if function_is_on_stack(test):
        print "nested"

@record_function_on_stack
def test():
    nested()

test()

これは、再帰、スレッド化、および例外を処理します。

私は次の 2 つの理由から、このアプローチは好きではありません。

  • 関数がさらに装飾されている場合は機能しません。これは最終的なデコレーターでなければなりません。
  • これをデバッグに使用している場合は、コードを 2 か所で編集して使用する必要があります。1 つはデコレータを追加するためのもので、もう 1 つはそれを使用するためのものです。スタックを調べるだけの方がずっと便利なので、デバッグしているコードのコードを編集するだけで済みます。

より良いアプローチは、スタックを直接調べて (おそらく速度向上のためのネイティブ拡張として)、可能であれば、スタック フレームの存続期間中、結果をキャッシュする方法を見つけることです。(ただし、Python コアを変更しない限り、それが可能かどうかはわかりません。)

于 2009-09-10T06:13:49.910 に答える