9

sys.exc_infoのドキュメントに、トレースバック オブジェクトを扱う際には注意するように記載されていることは承知していますが、いくつかのケースがどれほど安全かどうかについてはまだ不明です。さらに、ドキュメントには「警告: これを行わないでください!」と記載されており、その直後に「注: 実際には問題ありません」と記載されているため、さらに混乱します。

いずれにせよ、ドキュメントと「Python で sys.exc_info() トレースバックを明示的に削除する必要があるのはなぜですか?」 (Alex Martelli の回答) は、それらに割り当てられたトレースバック値を参照する唯一のローカル変数を暗示しているようです。問題。

これにより、いくつかの質問が残ります。

  1. このコンテキストでの「ローカル変数」とは、正確には何を意味するのでしょうか? 私は用語に苦労していますが、これは関数で作成された変数のみを意味するのでしょうか、それとも関数パラメーターによって作成された変数でもありますか? スコープ内の他の変数、たとえば globals や self はどうですか?
  2. クロージャーは、トレースバックの潜在的な循環参照にどのように影響しますか? 一般的な考えは次のとおりです。クロージャーは、それを囲む関数が参照できるすべてのものを参照できるため、クロージャーへの参照を含むトレースバックは、かなりの量を参照することになります。より具体的な例を思いつくのに苦労していますが、内部関数、sys.exc_info() を返すコード、スコープ内の高価で短命のオブジェクトの組み合わせです。

これを書いているときに、自分の発言を信じたり信じなかったりすることを何度も考えてきたので、私の結論や仮定のどこが間違っているか教えてください:)。

私の具体的な例への回答を求めていますが、一般的なアドバイス、知識、またはより難解な状況でトレースバックを安全に処理する方法に関する戦争の話も求めています (たとえば、ループを実行する必要があり、トレースバックを蓄積したい場合)。発生した例外、新しいスレッドを生成し、発生した例外を報告する必要がある、クロージャとコールバックを作成し、発生した例外を返信する必要がある、など)。

例 1: エラー処理を行う内部関数

def DoWebRequest():
  thread, error_queue = CreateThread(ErrorRaisingFunc)
  thread.start()
  thread.join()
  if not error_queue.empty():
    # Purposefully not calling error_queue.get() for illustrative purposes
    print 'error!'

def CreateThread(func):
  error_queue = Queue.Queue()
  def Handled():
    try:
      func()
    except Exception:
      error_queue.put(sys.exc_info())
  thread = threading.Thread(target=Handled)
  return thread, error_queue

トレースバックも含まれているため、Handled()閉鎖によって発生した例外が参照error_queueされ、循環参照が発生しますか? error_queueからのトレースバックの削除error_queue(つまり、呼び出し.get()) は、循環参照を排除するのに十分ですか?

例 2: exc_info のスコープ内にある長寿命のオブジェクト、または exc_info を返す

long_lived_cache = {}

def Alpha(key):
  expensive_object = long_lived_cache.get(key)
  if not expensive_object:
    expensive_object = ComputeExpensiveObject()
    long_lived_cache[key] = expensive_object

  exc_info = AlphaSub(expensive_object)
  if exc_info:
    print 'error!', exc_info

def AlphaSub(expensive_object):
  try:
    ErrorRaisingFunc(expensive_object)
    return None
  except Exception:
    return sys.exc_info()

の発生した例外にAlphaSub()は への参照がexpensive_objectあり、expensive_objectキャッシュされているため、トレースバックは消えませんか? もしそうなら、どうすればそのようなサイクルを断ち切ることができますか?

または、スタック フレームをexc_info含み、AlphaスタックAlphaフレームに への参照が含まれているためexc_info、循環参照が発生します。もしそうなら、どうすればそのようなサイクルを断ち切ることができますか?

4

1 に答える 1

4

このコンテキストでの「ローカル変数」とは、正確には何を意味するのでしょうか? 私は用語に苦労していますが、これは関数で作成された変数のみを意味するのでしょうか、それとも関数パラメーターによって作成された変数でもありますか? スコープ内の他の変数、たとえば globals や self はどうですか?

「ローカル変数」は、関数内で作成されるすべての名前バインディングです。これには、すべての関数パラメーターと、割り当てられたすべての変数が含まれます。たとえば、次のとおりです。

def func(fruwappah, qitzy=None):
    if fruwappah:
        fruit_cake = 'plain'
    else:
        fruit_cake = qitzy
    frosting = 'orange'

変数fruwappahqitzyfruit_cake、およびfrostingはすべてローカルです。ああ、self関数ヘッダーにあるので (私の例ではそうではありません;)、それもローカルです。

クロージャーは、トレースバックの潜在的な循環参照にどのように影響しますか? 一般的な考えは次のとおりです。クロージャーは、それを囲む関数が参照できるすべてのものを参照できるため、クロージャーへの参照を伴うトレースバックは、かなりの量を参照することになります。より具体的な例を考え出すのに苦労していますが、内部関数、sys.exc_info() を返すコード、スコープ内の高価で短命のオブジェクトの組み合わせです。

リンクした回答のように、トレースバックは、例外が発生したときにアクティブだったすべての関数 (およびその変数) を参照します。言い換えれば、関係するクロージャーがあるかどうかは関係ありません。クロージャー (非ローカル) に代入するか、さらに言えばグローバル変数に代入すると、循環参照が作成されます。

これに対処するには、次の 2 つの基本的な方法があります。

  1. 例外が発生した後に呼び出される関数を定義します。トレースバックにはスタック フレームがないため、トレースバックを含むすべての変数が終了すると、消えます。また
  2. 確認してくださいdel traceback_object

とはいえ、私自身のコードにはまだ traceback オブジェクトが必要です。これまでのところ、Exception とそのさまざまな属性で十分です。

于 2011-09-11T07:27:49.083 に答える