こんな人にオススメ
2021年10月4日にpython3.10がリリースされ、新機能としてパターンマッチが追加されたけど、どんなもの?
ということで、今回はpython3.10で新たに追加された新機能「パターンマッチ」について解説する。「match
」と「case
」を使用して処理を分岐させる構文だ。
似ているものとしてはC言語のswitch
文が挙げられる。今までpythonにはこういった条件分岐はなかった。
なお、今回のパターンマッチを使用しなくても、ifを使用することで代用することが可能。だけど、複雑な構造を簡単にかけるっぽい。
python環境は以下。
- Python 3.10.0
基本的な書き方
match 調べたいオブジェクト: case パターン1: 処理1 case パターン2: 処理2 case _: パターン1, 2以外の場合の処理
基本的な書き方はシンプルで、本当にC言語のswitch文と同じイメージ。ただし、switch
とは書かずにmatch
と書く。
case
の後に判定したいパターンを入れていき、最後のcase _
でパターンに当てはまらないもの全ての処理を行う。
最後のcase _
はなくても大丈夫。そのときはパターンから外れると何も起きない。後ほど実行する。
数値でマッチ
# パターンマッチ def match_simple(val): match val: case 0: # valが0の時 print('0でした') case 1: # valが1の時 print('1でした') case _: # valが0, 1以外の時 print('0, 1以外')
まずは数字でパターンマッチする。判定するのは以下の項目。
- 0
- 1
- 上記以外
なお、小数1.
を入れたとしても1
判定される。
val = 0 # 調べたい変数 match_simple(val) # 0でした match_simple(val=10) # この場合はマッチしないので「case _」に該当 # 0, 1以外 match_simple(val=1.) # 小数も適用される # 1でした
or
は「|
」で指定
# もしくはの時は「|」を使用 def match_or(val): match val: case 0 | 1: # valが0か1の時 print('0もしくは1でした') case 1: # valが1の時(上のcaseで1は取られるのでここは適用されない) print('1でした') case _: # valが上記以外の時 print('0, 1以外')
パターンに「または」を入れたいときは「|
」を使用する。上では0
もしくは1
で判定をしている。
なお、パターン1ですでに1
が判定されているので、パターン2の1
は判定されない。
match_or(val=0) # 0もしくは1でした match_or(val=1) # 0もしくは1でした match_or(val=2) # 0, 1以外
or
にするとエラー
# 「|」の代わりに「or」を使用するとエラー def match_or2(val): match val: case 0 or 1: # valが0か1の時 print('0もしくは1でした') case 1: # valが1の時(上のcaseで1は取られるのでここは適用されない) print('1でした') case _: # valが上記以外の時 print('0, 1以外') # case 0 or 1: # valが0か1の時 # ^^ # SyntaxError: expected ':'
逆に「|
」の代わりに「or
」を使用するとこれは文法エラーとなる。
andはそもそもエラー
# andは使えない def match_and(val): match val: case 0 & 0.: # valが0の時 print('0でした') # case 0 & 0.: # valが0の時 # ^ # SyntaxError: expected ':'
and
の場合はどうかというとこちらも文法エラー。andを使用する例が思いつかなかったので意味わからん判定になっているけどエラー。
後述するがパターンには式は入れられないので、len(val) == 3 and val[0] == 'a'
のように文字列の長さと先頭文字の判定を入れることはできない。
上の例では「&
」で試したけど、「and
」にしてもエラーだった。
case _
を省略するとそれ以外は何もしない
# 「case _」を省略するとそれ以外の時には何もしない def match_no_others(val): match val: case 0 | 1: # valが0か1の時 print('0もしくは1でした') case 1: # valが1の時(上のcaseで1は取られるのでここは適用されない) print('1でした')
それ以外のパターンであるcase _
を省略することも可能。この場合はそれ以外のパターンでは何もしないことになる。
今回の例だと、0
か1
以外の入力では何も起きないことになる。
match_no_others(val=0) # 0もしくは1でした match_no_others(val=2) # 「case _」を省くとそれ以外の判定は何もしないことになる
式は入れられないけど複素数なら大丈夫
# 式は入れられない val = 1 match val: case 0 + 1: print('0 + 1を判定') # case 0 + 1: # ^ # SyntaxError: imaginary number required in complex literal
上で少し書いたけど、パターンの部分に式を入れる事はできない。この例だと0 + 1
という式を入れたけどエラー。
しかし、式ではなく複素数とするとエラーとはならない。以下の例では0 + 1j
という複素数を使用した。まあ実数と虚数だから分けざるを得ない。
# 複素数なら大丈夫 val = 0 + 1j match val: case 0 + 1j: print('0 + 1jを判定') # 0 + 1jを判定
if
を使うと条件追加(ガード)
# ifを使って条件を追加(ガード) def match_req(val): match val: case val if val <= 1: print('1以下の値') case val if 1 < val <= 2: print('1 < val ≤ 2の値') case _: print('それ以外')
case
のパターンに条件を追加することも可能。これをガードと呼ぶようだ。かっこいい。
これは、case
のパターンに当てはまったものに追加で条件を追加するイメージ。今回だとval
が1
以下かどうかと1
を超過して2
以下とした。
match_req(val=0) # 1以下の値 match_req(val=1) # 1以下の値 match_req(val=1.5) # 1 < val ≤ 2の値 match_req(val=2) # 1 < val ≤ 2の値 match_req(val=3) # それ以外
list
のマッチは個数判定
# listでマッチさせるときは要素数で判定 def match_lst(lst): match lst: case[1]: print('[1]でした') case[x]: print(f"[1]ではなく[{x}]でした") case[0, 1]: print('[0, 1]でした') case[x, y]: print(f"[0, 1]ではなく[{x}, {y}]でした") case _: print('それ以外')
list
をパターンに持ってくることも可能。考え方は今までと同じでいける。なお、tuple
も同じように可能。
初めのcase
で[1]
を判定し、それ以外の要素数が1つのリストはパターン2の[x]
で判定している。その後も同じ感じ。
match_lst(lst=[0]) # [1]ではなく[0]でした match_lst(lst=[1]) # [1]でした match_lst(lst=[0, 1]) # [0, 1]でした match_lst(lst=[10, 11]) # [0, 1]ではなく[10, 11]でした match_lst(lst=[10, 11, 20]) # それ以外
dict
のマッチはkey
判定
# 辞書型ではkeyで判定 def match_dct(dct): match dct: case {'key1': 0, 'key2': x}: print('0番目のケース') case dict(key1=1, key2=x): # dict()で書くとスルーされる print('1番目のケース') case {'key1': 1, 'key2': x}: print('2番目のケース') case _: print('それ以外')
dict
もパターンに持ってこれる。dict
の場合はちょっと挙動が変になるので、マッチさせる際には注意が必要。
{key:: value}
で作成したdictだとちゃんと判定されるけど、dcit(key=value)
で作成すると判定されない。
色んなサイトの例を見ても{}
を使用するばかりだったので、下手にdictで作成しないほうがいいだろう。
match_dct(dct={'key1': 0, 'key2': 10}) # 0番目のケース match_dct(dct={'key1': 1, 'key2': 10}) # 2番目のケース match_dct(dct=dict(key1=1, key2=10)) # 2番目のケース match_dct(dct={'key1': 2, 'key2': 10}) # それ以外
型で判定
# 型で判定 def match_type(x): match x: case int(): print('整数') case str(): print('文字列') case list(): print('リスト') case tuple(): print('タプル') case dict(): print('辞書')
最後は型の判定。case
にはクラス名で入れることもできる。ここでは簡単な型を判定させてみた。
match_type(x=10) # 整数 match_type(x='10') # 文字列 match_type(x=[10]) # リスト match_type(x=(10,)) # タプル match_type(x={'10': 10}) # 辞書
下手に使うことはないか
今回はpython 3.10の新機能であるパターンマッチを試してみた。これまではif
を使ってちまちま書いていたのがmatch
を使用することで簡単に書ける。
しかし、実際にはまだこれを活用する場面が思い浮かばないし、下手に使うと他の人が使えないってことになりかねない。
ということで、パターンマッチはしばらくは使わないほうがいいか、自分用に使うのがいいだろう。
関連記事
-
-
【辞書の結合】dictのマージ
続きを見る
-
-
【astropy&単位】astropyの単位出力書式
続きを見る
-
-
【python3.7以降&dictのkeys】ネスト(入れ子)されたdictのkeys一覧をカッコで出力
続きを見る
-
-
【python3&zip関数】python3系にてzipは一回使ったら消える
続きを見る