Python 3.10の新機能:with文の強化/エラーメッセージの改善/EncodingWarningクラス:Python最新情報キャッチアップ
ネストしたコンテキストマネジャーを簡潔な記述、分かりやすくなったエラーメッセージ、EncodingWarningクラスを取り上げる。
2021年10月4日にPython 3.10がリリースされた。主要な新機能や変更点をかいつまんでまとめると以下のようになる(詳しくは「What's New In Python 3.10」を参照されたい)。
- ネストしたコンテキストマネジャーの簡潔な記述
- 分かりやすくなったエラーメッセージ
- デバッグやプロファイリング用に提供される行番号がより正確なものに
- 構造的パターンマッチ
- ファイル操作などでエンコーディングを明示しなかった際にEncodingWarning警告クラスを送出するようにオプトイン可能
- 「|」演算子を使用したユニオン型の指定
- パラメーター仕様変数
- 型エイリアス
- ユーザー定義の型ガード
前回はこれらのうちの構造的パターンマッチについて見た。今回は、その他の項目の中から幾つかを紹介する。
ネストしたコンテキストマネジャーの簡潔な記述
Python 3.8まではコンテキストマネジャーをネストさせる(例えば、複数のファイルを同時に開くような)ときには、次のどちらかのような書き方をする必要があった。
# with文をネストさせる
with open('foo.txt') as foo:
with open('bar.txt') as bar:
print(foo.read())
print(bar.read())
# with文のコンテキスト式をカンマ区切りで並べる
with open('foo.txt') as foo, open('bar.txt') as bar:
print(foo.read())
print(bar.read())
「with」に続けて記述するコンテキスト式は上の2つ目の例のように、カンマで区切って複数並べられるが、それらを複数行に分割して書くことはサポートされていなかった。そのため、コンテキスト式がもっと多くなれば(あるいは上の例ならファイル名がもっと長ければ)、1行が非常に長くなってしまう(PEP 8では1行の文字数は79文字までとなっている)。
上記の2つ目のwith文でコンテキスト式を複数行に分割して記述したければ、明示的な行継続を使って次のようにも書ける。
with open('foo.txt') as foo,\
open('bar.txt') as bar:
print(foo.read())
print(bar.read())
こうすれば行を分割して継続できるが、バックスラッシュがあるのが煩わしいという方は多いはずだ。ここでPythonではかっこ「()」などで囲まれている部分(関数の引数など)は複数行に分割して記述できるので(非明示的な行継続)、with文でコンテキスト式をかっこで囲めばよさそうだが、このような書き方は、Python 3.8までの古いパーサーの制限でできなかった。
with (open('foo.txt') as foo,
open('bar.txt') as bar):
print(foo.read())
print(bar.read())
Python 3.9では新しいパーサーが採用された。そのメリットの1つが上記のような記述が可能になったことだ。
Python 3.9では古いパーサーも残されていたが、「What's New In Python 3.9」に「In Python 3.10, the old parser will be deleted and so will all functionality that depends on it」(Python 3.10では古いパーサーは取り除かれ、古いパーサーに依存する機能も全て取り除かれることになる)とある通り、Python 3.10では以前のパーサーが削除されている。
古いパーサーが取り除かれたからか、Pythonのドキュメントではwith文の記述も変わっている(Python 3.9のもの、Python 3.10のもの)。Python 3.10のwith文の説明では最後に「Support for using grouping parentheses to break the statement in multiple lines」(グループ化のかっこを使って、with文を複数行に分割することをサポート)ともあることから、上で見たようなかっこを使ってコンテキスト式を複数行に分割して記述することが、Python 3.10から公式にサポートされたと考えられる。
分かりやすくなったエラーメッセージ
Python 3.10ではそれまでのバージョンのPythonよりもエラーメッセージが分かりやすいものになっている。
例えば、if文で節の最後にコロン「:」を付け忘れたとしよう。
num = 100
if num == 100
print(f'{num} is 100')
このコードをREPL環境で実行すると、Python 3.9では次のような結果になる。
エラーが2つ発生しているが、ここでは範囲選択をして反転表示されている部分に注目されたい。「SyntaxError: invalid syntax」というのだから、構文が間違っていることは分かる。が、具体的にどこが間違っているかまでは分からない。一方、Python 3.10のREPL環境で実行した結果は次のようになる。
こちらは「SyntaxError: expected ':'」とコロンがないことを教えてくれる。
もう1つ、[0, 1, 2]と['foo', 'bar', 'baz']という2つのリストがあり、0と'foo'、1と'bar'、2と'baz'のように、同じインデックス位置にあるものを要素とするタプルを作り、それをリストに格納したいとしよう。
以下はこれを行おうとして書いたバグ入りのコードだ。
int_list = [0, 1, 2]
str_list = ['foo', 'bar', 'baz']
result = [item0, item1 for item0, item1 in zip(int_list, str_list)]
先ほどと同じく、Python 3.9のREPL環境で実行した結果から示そう。
「SyntaxError: invalid syntax」では意味がよく分からない。一方、Python 3.10の場合は次のようになる。
こちらは「SyntaxError: did you forget parentheses around the comprehension target?」(内包表記のターゲットをかっこで囲むのを忘れていませんか?)とあるので、どこでやらかしたかが分かる。つまり、先のコードは以下のようにするのが正解だ。
int_list = [0, 1, 2]
str_list = ['foo', 'bar', 'baz']
result = [(item0, item1) for item0, item1 in zip(int_list, str_list)]
print(result) # [(0, 'foo'), (1, 'bar'), (2, 'baz')]
タプルを囲むかっこは省略できるので、いつものように省略していたのだが、内包表記のターゲットとして記述するときには省略はできなかったということだ。
ここでもう1つ注意したい点がある。上の画像(Python 3.10のREPL環境での実行画面)を見ると、エラーが発生した箇所を示す「^」が「item0, item1」全体を示すようになっている点だ。以前なら「^」が1つだけ表示されていたのが、Python 3.10からはエラーの対象となる部分全体を示してくれるようになっている。
この他にもエラーメッセージに対してなされた改善点は多数ある。詳細は「Better error messages」を参照してほしい。
EncodingWarning警告クラス
Copyright© Digital Advantage Corp. All Rights Reserved.