RecursionError は RecursionError とは限らない、という話
先日、 Pythonjp Discordサーバ の初心者部屋で、こんな質問 があった。
次のようなコードが RecursionError
例外を出して困っているという。
if a: ... elif b: ... elif c: ... elif d: ...
のように、数千個の elif
が並んでるコードだ。
多くの場合、RecursionError
は、関数が自分自身を繰り返し呼び出す 再帰呼び出し で発生する。こんな感じだ。
>>> def f(): ... f() ... >>> f() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in f File "<stdin>", line 2, in f File "<stdin>", line 2, in f [Previous line repeated 996 more times] RecursionError: maximum recursion depth exceeded >>>
しかし、問題のコードには、再帰呼出しはもちろん関数呼び出しも存在しない。なぜ RecursionError
例外が発生するのだろうか?
関数呼び出しのないRecursionError
問題のコードを実行すると、次のようなエラーが出る。
$ python3 foo.py RecursionError: maximum recursion depth exceeded during compilation
実は、このエラーはPythonスクリプトを実行して発生しているのではない。ソースコードをコンパイルするときに、Python言語のコンパイラで発生している。
Pythonは、ソースコードをコンパイルするときに 抽象構文木(AST) というツリー状のデータ構造を作る。この AST を処理するときに再帰的に処理を行うため、ツリーのネストが深すぎると RecursionError
例外が発生する。
このケースでは if 〜 elif 〜 elif 〜
が大量に並んでおり、if
文の子ノードとして elif
ノードが作られ、その elif
ノードの子ノードとしてまた elif
ノードが作られ、その elif
ノードの子ノードとして…… という風に、elif
の数だけツリーが深くなっている。
このため、ソースコードをコンパイルする過程で行われる再帰呼び出しの回数が制限を超えてしまい、RecursionError
例外 が発生してしまうのだ。
対処方法
関数呼び出しがまったく存在しないソースコードであっても、Pythonコンパイラの制約により RecursionError
例外が発生してしまうことがある。この例では極端に長い if 〜 elif 〜 elif 〜
ブロックだったが、他にも ((((......(a)+1)+1)+1)......)
のようなコードでも発生する場合がある。
このようなエラーが出てしまったら、ASTが深くなりすぎないようなコードに修正する必要がある。
if 〜 elif 〜 elif 〜
ブロックなどが長すぎる場合は、短く分割する。長すぎる式は複数行に分割する、などの変更ですむので、対処はそれほど難しくない場合が多いと思う。