検索
連載

Python 3.10の新機能:with文の強化/エラーメッセージの改善/EncodingWarningクラスPython最新情報キャッチアップ

ネストしたコンテキストマネジャーを簡潔な記述、分かりやすくなったエラーメッセージ、EncodingWarningクラスを取り上げる。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
「Python最新情報キャッチアップ」のインデックス

連載目次

 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文の書き方(Python 3.8まで)

 「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())

明示的な行継続でコンテキスト式を2行に分割

 こうすれば行を分割して継続できるが、バックスラッシュがあるのが煩わしいという方は多いはずだ。ここでPythonではかっこ「()」などで囲まれている部分(関数の引数など)は複数行に分割して記述できるので(非明示的な行継続)、with文でコンテキスト式をかっこで囲めばよさそうだが、このような書き方は、Python 3.8までの古いパーサーの制限でできなかった。

with (open('foo.txt') as foo,
      open('bar.txt') as bar):
    print(foo.read())
    print(bar.read())

Python 3.8まではこのような書き方は、パーサーの制限で許されなかった

 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では次のような結果になる。

Python 3.9のREPL環境で上のバグ入りコードを実行した結果
Python 3.9のREPL環境で上のバグ入りコードを実行した結果

 エラーが2つ発生しているが、ここでは範囲選択をして反転表示されている部分に注目されたい。「SyntaxError: invalid syntax」というのだから、構文が間違っていることは分かる。が、具体的にどこが間違っているかまでは分からない。一方、Python 3.10のREPL環境で実行した結果は次のようになる。

Python 3.10のREPL環境で上のバグ入りコードを実行した結果
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環境で実行した結果から示そう。

Python 3.9のREPL環境での実行結果
Python 3.9のREPL環境での実行結果

 「SyntaxError: invalid syntax」では意味がよく分からない。一方、Python 3.10の場合は次のようになる。

Python 3.10のREPL環境での実行結果
Python 3.10のREPL環境での実行結果

 こちらは「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.

[an error occurred while processing this directive]
ページトップに戻る