Python 3.14では例外に関して2つの点が改善されています。1つのexcept節で複数の例外クラスを補足する場合に、それらの列挙が簡潔になったことと、finally節にreturn文などを置くとSyntaxWarningが発生するようになったことがそうです。これらについて見てみましょう。
Python 3.14では例外に関連して2つの分布上の改善/安全性向上が行われている。
以下ではこれら2つについてまとめる。
どうもHPかわさきです。
約1カ月続けてきた自称「Python祭り」ですが、今回の記事で取りあえずの終了となります。Python 3.14の新機能については本記事を含めて6本を公開しました。
結構頑張ったつもりなんですが、どうなんでしょ。あまり人気企画にはならなかった印象なのですが、「新機能なんてどうでもいいよ」って方や、「ちょっとマニアックすぎて今回はパス」って方も結構いらっしゃるんですかねぇ。うーむ(遠い目)。でも、Pythonの言語仕様も毎年少しずつ変化していくので、新機能が出たときに軽く触れておく方が、無理なく知識を積み上げられて楽かなと思います。もしまだ知らない情報があれば、ぜひ他の記事もご笑覧ください。
Python 3.13までは例外を補足するexcept節(または例外グループに含まれる例外のいずれかを補足する)で複数の種類の例外クラスを記述する場合はかっこ「()」内にそれらを並べる必要があった。以下はその例だ。
from random import randint
def raise_exception():
exceptions = [TypeError(0), OSError(1), ValueError(2)]
raise exceptions[randint(0, 2)]
def raise_and_catch():
try:
raise_exception()
except (TypeError, ValueError):
print('type error or value error')
except OSError:
print('OS error')
1つ目のexcept節ではタプルを使って「(TypeError, ValueError)」のように複数の例外クラスを列挙している点に注目しておこう。そして、Python 3.14では例外クラスの列挙の後に「as」がない場合には、複数の例外クラスを列挙する際にかっこで囲む必要がなくなった。
from random import randint
def raise_exception():
exceptions = [TypeError(0), OSError(1), ValueError(2)]
raise exceptions[randint(0, 2)]
def raise_and_catch():
try:
raise_exception()
except TypeError, ValueError:
print('type error or value error')
except OSError:
print('OS error')
raise_and_catch()
先ほど複数の例外クラスの指定にタプルを使っていると述べた。ここでタプルの性質を思い出してほしい。それは「タプルをタプルとしているのは、それを囲むかっこではなく、要素を列挙するためのカンマである」ということだ。簡単な例を示す。
t = (1,)
t = 1,
x, y = 1, 2
1行目の例は1要素のタプルを作成しているが、2行目のようにかっこを省略できる。むしろ、これがタプルであることを示すのはカンマである。3行目では代入文の左辺も右辺もタプルだが、かっこを省略して表記している。
except節(except*節)でも同じように、タプルを囲むかっこを省略できるようになったというのがPEP 758の提案といえる。これはPEP 758でも「This change would bring the syntax more in line with other comma-separated lists in Python, such as function arguments, generator expressions inside of a function call, and tuple literals, where parentheses are optional.」と述べられている。適当に訳すと「この変更により、Pythonにおけるカンマ区切りの列挙構文との一貫性がより高まります。例えば、関数の引数、関数呼び出しに含めるジェネレータ式、タプルリテラルなど、かっこを省略できる場面がそうです」とでもなるだろう。
かっこを省略できることには、わずかだがコードが読みやすくなるという効果もあるだろう。
ただし、asを付ける場合にはこれまでと同様に、複数の例外クラスをかっこで囲む必要がある。
def raise_and_catch():
try:
raise_exception()
except TypeError, ValueError as e: # SyntaxError
print('type error or value error')
except OSError:
print('OS error')
実際に試した結果を以下に示す。
このときには、以下に示すようにかっこを使う。
def raise_and_catch():
try:
raise_exception()
except (TypeError, ValueError) as e: # OK
print('type error or value error')
except OSError:
print('OS error')
なお、asの使用に関係なく、これまでのように複数の例外クラスをかっこで囲んでも何ら問題はない。以前のコードもこれまで通りにPython 3.14で動くはずだ。
パッと見では「細かいなぁ」と思える改善点ですが、タプルなどの表記に見られる省略してもよいところでは、かっこを省略して記述できるというルールとの一貫性を取るようにしたと考えると、なかなか趣深いですね(謎の上から目線)。
PEP 765はfinally節でのreturn文/break文/continue文の使用を制限し、より安全なコードを書けるようにするものだ。Python 3.14ではSyntaxWarningが発生する。「SyntaxError例外が発生するようになるかどうか」「そうだとしていつからそうなるか」については未定だ。
finally節にこれらの文を置くと、それまでに発生した例外が握りつぶされたり、try節から返されたはずの値が上書きされたりする可能性がある。これは混乱の素であり、バグを生み出す要因ともなる。そのために、Python 3.14ではそうしたコードについてはSyntaxWarningを発生させるようになった。
簡単な例を見てみよう。
def foo():
foo()
foo()
foo関数は自身を再帰的に呼び出しているので、いつかはコールスタックがあふれて、例外が発生する。
では、try文を使ってこのコードを次のようにしてみよう。
def foo():
try:
foo()
finally:
return
foo()
Python 3.13でこのコードを実行すると、次のようにRecursionError例外が握りつぶされてしまう。
Python 3.14の構文ハイライトを見慣れると、Python 3.13のREPL(PyREPL)が色あせたものに見えてきますね(笑)。
一方、Python 3.14では次のコードを実行してみると、foo関数の定義が終わった時点でSyntaxWarningが発生する。ただし、foo関数自体は例外を発生させることなく終了する。
このようにSyntaxWarningを発生させることでfinally節に危険なコードが含まれていることが分かるようになった。
次のコードは、try節では0を返しているが、finally節では1を返している。
def foo():
try:
return 0
finally:
return 1
foo()
finally節は必ず実行されるので、try節で返そうとした値がfinally節にあるreturn文で上書きされてしまう。このような場合にもPython 3.14では関数定義の時点でSyntaxWarningを発生させるようになった。
break文やcontinue文でも同様だ。
そもそも、finally節にこれらの文を記述することに問題がある。finally節ではリソースの解放といった後片付けに相当する処理だけを記述することを心がけるべきだが、コードを書いていると、いつの間にかそういうコードが出来上がっていることもある。SyntaxWarningを発生させるようになったことで、そうした間違いに気付くタイミングが得られるようになり、プログラムのより安全な動作が期待できるようになる。
PEP 765に関連するサンプルコードを考えていると、「いやぁ、そうは書かないでしょ」というコードばかりが思い浮かんでいました。だって、for文の中にあるtry文のfinally節でbreakするコードなんて書きそうもないじゃないですか。と思っていても、いつかそういう場面に出会うことになるのかもしれません。そんなときのために転ばぬ先の杖としてSyntaxWarningがあるのはよいですね。
Copyright© Digital Advantage Corp. All Rights Reserved.