これは興味深い問題です。期待される動作が現実的ではない理由は、RuntimeError が発生したときに、問題のある「再帰的すぎる」スタック フレームが閉じられているようです。これは、例外が次に低いスタック フレームでキャッチされると、そのフレームは制限に達するまで再び上方に再帰できることを意味します。
つまり、再帰の深さ (N など) に達するとすぐに、次のようになると予想していました。
- RuntimeError をスローする
- (N-1)番目以外のブロックにジャンプ
- 別の RuntimeError を取得する
- を除いて (N-2) 番目にジャンプ
- 等
実際に何が起こるかは次のとおりです。
- RuntimeError をスローする
- (N-1)番目以外のブロックにジャンプ
- N までずっと再帰する
- 別の RuntimeError を取得する
- を除いて (N-2) 番目にジャンプ
- N までずっと再帰する
- 等
さらに、それぞれの「N までずっと再帰する」は、例外-再帰-例外の同じインクリメンタル プロセスを使用してアンワインドする必要があります。したがって、予想よりもはるかに多くの再帰があります。
x
出力で確認するのが難しい理由は、元のコードが同じ値を持つ複数の呼び出しを区別しないためです。1001 回目の呼び出しが行われると、1000 回目の呼び出しで例外が発生し、999 回目の呼び出しに制御が戻ります。この呼び出しは、 を使用して別の呼び出しを行いx=1000
、特定の値を持つ呼び出しの並列「系列」を作成しx
ます。
この動作は、元のコードを次のように変更することで確認できます。
y = 2000
def a(x=0, t=''):
print(t + "In a({0})".format(x))
global y
if y:
y -= 1
try:
a(x+1, t)
except RuntimeError:
print(t + "*** E: %d" % x)
a(x+1, t+'\t')
これによりインデントが追加されるため、どの呼び出しが他のどの呼び出しの中に入ったかを確認できます。結果の出力のサンプルは次のとおりです。
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 984
In a(985)
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 983
In a(984)
In a(985)
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 984
In a(985)
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
(何らかの理由で、私のインタープリターは 1000 回目の呼び出しではなく 988 回目の呼び出しで最初にエラーを生成しますが、これはあまり変わりません。) すべてのエラーは、階層内の 1 つのステップだけを元に戻し、フォレスト全体を許可することがわかります。 「ネストされた再帰」が発生します。
これにより、コール数が指数関数的に増加します。実際、再帰制限を小さな値に設定してこれをテストし (20 と 25 を試しました)、再帰が最終的に終了することを確認しました。私のシステムでは、2**(R-12)
合計呼び出し後に終了します。ここで、R は再帰制限です。(12 は、最初の例外が N=988 で発生したときの私の例で見られるように、再帰制限と最初の例外が実際に発生した数との差です。おそらく、これらの 12 フレームは何らかの方法で内部的に「使用」されています。私の通訳)
インタプリタがハングしているように見えるのは当然のことです。制限が 1000 であると、完了するのに宇宙の年齢よりもはるかに時間がかかるためです。