はじめに
比較的新しい機能である構造的パターンマッチ文 (以下、マッチ文) を解説します。
初学者はまず if 文で条件分岐を身に付けるべきと考え、タイトルでは中級者向けと謳っています。
Python 3.10 以降の機能です
マッチ文の概要
他言語における switch 文のようなものです。
a = 0
if a == 0:
print("0")
elif a == 1:
print("1")
else:
print("other")
--> 0
a = 0
match a:
case 0:
print("0")
case 1:
print("1")
case _:
print("other")
--> 0
マッチ文を定義する手順
-
match
の後ろに値を記述して、:
で区切る -
case
の後ろに値を記述して、:
で区切る - 分岐の数だけ 2. を繰り返す
- どの条件にも当てはまらない処理は
case _:
の後に記述
Java の switch 文では分岐が多いと記述が簡潔になるとのことですが、Python はもとより if 文の記述が簡潔なため、マッチ文のメリットが薄いように思えます。
ですが以下のケースに当てはまる状況下では、その恩恵を享受できます。
OR パターン
|
で要素を列挙するパターンです。
mob = "villager"
match mob:
case "zombie" | "skeleton" | "creeper":
print("Hostile")
case "chicken" | "pig" | "villager":
print("Passive")
case "enderman" | "piglin":
print("Neutral")
--> Passive
if 文でいうところの or
で繋げるイメージです。
AS パターン
as
で条件を満足した要素と変数を束縛するパターンです。
mob = "villager"
match mob:
case "zombie" | "skeleton" | "creeper" as hostile:
print(hostile)
case "chicken" | "pig" | "villager" as passive:
print(passive)
case "enderman" | "piglin" as neutral:
print(neutral)
--> villager
ビルトインクラストのマッチは こちら を参照してください。
リテラルパターン
リテラルを利用した一般的な例です。
内部的には ==
(真偽値や None
は is
) で評価しています。
val = None
match val:
case 0:
print(0)
case "abc":
print("abc")
case """abcabc""":
print("abcabc")
case r"\t\n":
print(r"\t\n")
case b"abc":
print("byte-abc")
case True:
print(True)
case False:
print(False)
case None:
print(None)
case _:
print("other")
--> None
数値 (複素数)・文字列・真偽値・None
が利用できますが、式と f 文字列は記述できません。
val = 2
match val:
case 1 + 1:
print(2)
case _:
print("other")
--> SyntaxError
ちなみに複素数の場合のみ、+
や -
を利用できます。
複素数を使うタイミングなどほとんどないとは思いますが...
val = 1 + 2j
match val:
case 1 + 2j:
print("1+2j")
case 1 - 2j:
print("1-2j")
--> 1+2j
ちなみに実数は左側で虚数は右側と決まっています。
val = 1 + 2j
match val:
case 2j + 1:
print("1+2j")
case -2j + 1:
print("1-2j")
--> SyntaxError
キャプチャパターン
パターンに変数を指定することで、常にマッチを成功させ値をその変数に束縛します。
mob = "slime"
match mob:
case name:
print(name)
--> slime
後述するパターンと組み合わせることで、真価を発揮します。
ワイルドカードパターン
前述した _
のことです。
最後に記述することで、else
のように振舞います。
mob = "slime"
match mob:
case _:
print(_)
注意点はキャプチャパターンより優先されるため、_
に値が束縛されないことぐらいでしょうか。
つまり上では case _
の _
と mob
は紐づいていませんし、そもそも case _
と print(_)
の _
は別物なので、なにも表示されません。
バリューパターン
オブジェクトの属性と値を比較します。
.
が付いていると、バリューパターンと認識されます。
class Pokemon:
def __init__(self, name):
self.name = name
pikachu = Pokemon("Pikachu")
name = "Pikachu"
match name:
case pikachu.name:
print(name)
--> Pikachu
グループパターン
()
でパターンの可読性を高めるだけです。
tool = "chainsaw"
match tool:
case "auger" | "chainsaw" | "impact driver":
print(tool)
--> chainsaw
match tool:
case ("auger" | "chainsaw" | "impact driver"):
print(tool)
--> chainsaw
この程度の構文であれば、可読性に差は感じません。
AS パターンを併用した例で確認すると、効果を実感できそうです。
mob = "villager"
match mob:
case ("zombie" | "skeleton" | "creeper") as hostile:
print(hostile)
case ("chicken" | "pig" | "villager") as passive:
print(passive)
case ("enderman" | "piglin") as neutral:
print(neutral)
シーケンスパターン
リストやタプルといったシーケンス系にマッチするパターンです。
lst_1 = [0, 1, 2]
match lst_1:
case [0, 1, 2]:
print(lst_1)
--> [0, 1, 2]
tup_1 = (0, 1, 2)
match tup_1:
case (0, 1, 2):
print(tup_1)
--> (0, 1, 2)
これでは if 文で事足りますが、下のようなケースではかなり簡潔に記述できます。
lst_2 = [0, 1]
match lst_2:
case [0, x]:
print(x)
--> 1
[0, x]
は 0
から始まる 2 要素のリストであれば、次の要素を x
に束縛するという意味です。
キャプチャパターンとの合わせ技になっているのがミソです。
lst_2 = [0, 1]
if len(lst_2) == 2 and lst_2[0] == 0:
print(lst_2[1])
--> 1
どうでしょう、マッチ文のほうがかなり読みやすくはないでしょうか?
要素を増やしたもう少し複雑な例も確認します。
lst_1 = [0, 1, 2]
match lst_1:
case [0, 1, x]:
print(x)
--> 2
lst_1 = [0, 1, 2]
if len(lst_1) == 3:
if lst_1[0] == 0 and lst_1[1] == 1:
print(lst_1[2])
--> 2
このレベルだと if 文の可読性は酷いものです。
if 文と len()
を同時に利用する場合は、ぜひマッチ文も検討してください
複数の変数
パターン内で複数の変数を利用する際には、別の変数を記述してください。
lst_1 = [0, 1, 2]
match lst_1:
case [0, x, y]:
print(x)
print(y)
--> 1
2
可変長の要素
関数定義と同様に、要素が可変長であれば *
を記述します。
lst_1 = [0, 1, 2]
match lst_1:
case [0, *values]:
for value in values:
print(value)
--> 1
2
マッピングパターン
ディクショナリのようなマッピング系にマッチするパターンです。
mob = {"name": "ghast", "health": 10, "width": 4.0}
match mob:
case {"name": "ghast"}:
print(mob)
--> {"name": "ghast", "health": 10, "width": 4.0}
指定した要素が含まれるかのみで判断するため、他の要素が存在してもマッチすることは注意してください。
AS パターンとの合わせ技で、下のような型の判定のような記述も可能となります。
mob = {"name": "ghast", "health": 10, "width": 4.0}
match mob:
case {"name": str() as name}:
print(name)
--> ghast
str()
のようなビルトイン関数は下のようにも記述できます。
mob = {"name": "ghast", "health": 10, "width": 4.0}
match mob:
case {"name": str(name)}:
print(name)
--> ghast
mob = {"name": "ghast", "health": 10, "width": 4.0}
if "name" in mob and isinstance(mob["name"], str):
print(mob["name"])
シーケンスパターンほどの差はないですが、マッチ文だと意図が明瞭にみえます。
if 文と isinstance()
を同時に利用する場合は、ぜひマッチ文も検討してください
他の要素をキャプチャ
関数定義のキーワード可変長引数と似ていますが、**
を記述するだけです。
mob = {"name": "ghast", "health": 10, "width": 4.0}
match mob:
case {"name": str(name), **features}:
print(features)
--> {"health": 10, "width": 4.0}
クラスパターン
クラス系にマッチするパターンです。
今まで同様にパターン内の変数に値を束縛します。
class Pokemon:
def __init__(self, name, type, id):
self.name = name
self.type = type
self.id = id
pikachu = Pokemon("Pikachu", "Electric", 25)
match pikachu:
case Pokemon(name="Pikachu", type=type, id=id):
print(type)
print(id)
--> Electric
25
pikachu = Pokemon("Pikachu", "Electric", 25)
if isinstance(pikachu, Pokemon):
print(pikachu.type)
print(pikachu.id)
--> Electric
25
やはりマッチ文のほうが直感的かつ明瞭にみえます。
位置引数によるマッチング
実はデフォルトだと位置引数によるマッチングができません。
対処法は簡単で、クラス定義に __match_args__
を追加するだけです。
class Pokemon:
def __init__(self, name, type, id):
self.name = name
self.type = type
self.id = id
__match_args__ = ("name", "type", "id")
pikachu = Pokemon("Pikachu", "Electric", 25)
match pikachu:
case Pokemon("Pikachu", type, id):
print(type)
print(id)
--> Electric
25
__match_args__
に属性名を文字列としてタプルで渡すだけです。
ガード
case
の後ろに if
を記述することで、複雑な条件に柔軟に対応できます。
下ではガラル地方のポケモン (id
が 810 ~ 898) のみ表示するために、パターンの後に if 文を追加して、制限を設けることでより細かな条件を指定しています。
class Pokemon:
def __init__(self, name, type, id):
self.name = name
self.type = type
self.id = id
__match_args__ = ("name", "type", "id")
pikachu = Pokemon("Pikachu", "Electric", 25)
cinderace = Pokemon("Cinderace", "Fire", 815)
match pikachu:
case Pokemon(name, type, id) if 810 <= id <= 898:
print(type)
print(id)
match cinderace:
case Pokemon(name, type, id) if 810 <= id <= 898:
print(type)
print(id)
--> Fire
815
エースバーン (英名 : Cinderace) は if 文の条件を満足するため、タイプと図鑑 No. (ここでは id
) が表示されます。
まとめ
if 文でも記述はできるので、マッチ文に触れたことない方も多かったかもしれません。
とはいえ前述した 10 のパターンとガードさえおさえれば、マッチ文のスペシャリストといっても過言ではありません。
今後条件分岐を記述する際は、マッチ文と if 文を使い分けてロバストなコーディングを目指してください。
更新履歴
2025/04/23 初版
2025/04/24 改行を調整 & 誤植を修正