match文で、switch文に相当する条件分岐の記述が可能に[3.10]
ここからは、文法関連の機能を紹介していきます。C言語をはじめとする多くの言語には、値によって処理を分岐させるswitch文があります。これに相当するmatch文が、Python 3.10で導入されました。Pythonのリリース以来switch文が長い間存在していませんでしたが、ここに来て他の言語と同様な条件分岐を記述できるようになりました。
match文と基本的なパターン
match文は、C言語などにおけるそれによく似ています。以下のリストは、match文の基本型です。
match 値: case パターン1: 処理1 case パターン2: 処理2 …… case _: 全パターンに一致しなかったときの処理
指定される値が一致したパターンに対応する処理が実行されます。最後のパターン「_」(アンダースコア)はワイルドカードであり、C言語などのswitch文におけるdefaultに相当します。すなわち、いずれのパターンにも一致しなかった場合、ということになります。ワイルドカードが指定されていない場合、一致するパターンがないとmatch文は何もしない文となります。また、処理を区切るbreak文のようなものも不要です。
こう見ると、Pythonでmatch文を使うのは、switch文になじみがあれば非常に簡単です。以下のリストは、match文の典型的な例です。
month: int = 2 match month: case 1 | 3 | 5 | 7 | 8 | 10 | 12: print('31 days') case 4 | 6 | 9 | 11: print('30 days') case 2: print('28 or 29 days') case _: print('Bad month') # 実行結果:28 or 29 days
このように、パターンにリテラル値を記述すれば一致する処理が実行されます。リテラル値は、バー「|」で区切ることで「そのいずれか」を表現できます。match文のパターン指定はタプルやリスト、辞書にも対応したものとなっており、複雑なパターンマッチングが可能となっています。そのうち、タプルなどのシーケンスへのマッチングと、辞書へのマッチングを紹介します。
シーケンスへのマッチング
以下のリストは、タプルをパターンマッチングで仕分ける例です。関数に与えられるタプルは不定長であり、その長さは1番目の要素('line'など)で決まります。おのおののパターンでは、1番目の要素をリテラルで固定し、それで決まる2番目以降の要素を変数で指定しています。タプルの要素の個数が一致すれば、変数には実際の値が入るので、それを処理部分で参照できます。なお、1番目の要素に'line'を指定しても、さらに2個の要素が続くなどした場合にはパターンはマッチしません。リテラル部分が一致し、全体の個数が一致した場合のみマッチしたと見なされます。
def display(item): match item: case ('line', length): print(f'line: {length}') case ('triangle', bottom, height): print(f'triangle: {bottom}*{height}/2') case ('square', long_side, short_side): print(f'square: {long_side}*{short_side}') case ('trapezoid', top_side, bottom_side, height): print(f'trapezoid: ({top_side + bottom_side})*{height}/2') case _: print('Not supported') display(('line', 100)) display(('triangle', 100, 200)) display(('square', 200, 200)) display(('trapezoid', 100, 200, 50)) # 実行結果:line: 100 # triangle: 100*200/2 # square: 200*200 # trapezoid: (300)*50/2
辞書へのマッチング
タプルでは、要素のリテラルや個数でパターンを指定できました。これを辞書でやってみたのが以下のリストです。パターンは辞書の記法でそのまま記述しますが、変数を使って処理内で参照できる点はタプルと同様です。
def display(item): match item: case {'type': 'line', 'length': length}: print(f'line: {length}') case {'type': 'triangle', 'bottom': bottom, 'height': height}: print(f'triangle: {bottom}*{height}/2') case {'type': 'square', 'long_side': long_side, 'short_side': short_side}: print(f'square: {long_side}*{short_side}') case {'type': 'trapezoid', 'top_side': top_side, 'bottom_side': bottom_side, 'height': height}: print(f'trapezoid: ({top_side + bottom_side})*{height}/2') case _: print('Not supported') display({'type': 'line', 'length': 100}) display({'type': 'triangle', 'bottom': 100, 'height': 200}) display({'type': 'square', 'long_side': 200, 'short_side': 200}) display({'type': 'trapezoid', 'top_side': 100, 'bottom_side': 200, 'height': 50}) # 実行結果:line: 100 # triangle: 100*200/2 # square: 200*200 # trapezoid: (300)*50/2
with文の複数リソース指定が改良され、クローズ処理を自動化[3.10]
ファイルなどのリソースの解放を確実にするために、Pythonにはwith文があります。Javaのtry-with-resources文やC#のusing文に相当するもので、安全にリソースを使う際に便利な機能です。このwith文はPython 3.1で導入されて、その時点から複数のリソースが指定できました。以下のリストでは、3つのファイルを開いて内容を表示しますが、with文により自動的にファイルのクローズ処理が実行されます。
with open('hello.txt') as file1, open('konnichiwa.txt') as file2, open('bonjour.txt') as file3: print(file1.read()) print(file2.read()) print(file3.read())
この例では3つのファイルを指定していますが、ファイル数が増えてくるとwith文が長くなるのが欠点です。カンマのあとで改行するとエラーになりますし、バックスラッシュで行を続けるのもあまり見栄えのよいものではありません。
これを受けて、Python 3.10ではリソースの指定全体をカッコ()で囲むことで、カンマ後でのバックスラッシュなしの改行が可能になりました。
with (open('hello.txt') as file1, open('konnichiwa.txt') as file2, open('bonjour.txt') as file3): print(file1.read()) print(file2.read()) print(file3.read())
このように、インデントレベルは自由に調整できますし、バックスラッシュなども不要なので見た目にスッキリします。なお、この記法はPython 3.9ですでに利用可能でしたが、公式にドキュメントに掲載されたのは3.10からです。カッコで括る記法がタプルと同じであるので、サポートには新しいパーサが必要でした。そのパーサに完全に置き換えられたのがPython 3.10なので、公式には3.10からということになっています。
まとめ
今回は、Python 3.10以降の新機能のうち、ジェネリクスのための新しい型引数構文、**kwargsのより厳密な型付け、オーバーライドデコレータtyping.overrideなどのPython 3.12でサポートされた型関連機能、構造的パターンマッチング、with文の入れ子構文の改良などの文法関連の新機能を中心に紹介しました。
次回は、ExceptionGroupとTaskGroup、f-strings構文の形式化などの文法関連の新機能、それに改良されたエラーメッセージなどインタープリタの改良を中心に紹介します。