概要
ある日、下記のような(めんどくさい)処理を書いていました。
# int型かstr型かfloat型かNoneか、よく分からない何かを受け取る
number: Optional[int | str | float] = GetHoge()
if number is None:
# Noneのときの処理
...
elif isinstance(number, int):
# int型のときの処理
...
elif isinstance(number, str):
# str型のときの処理
...
elif isinstance(number, float):
# float型のときの処理
...
else:
# ここに来ることは現時点で考えられない
raise TypeError
設計が悪いと言えばそうなんだけど、もっと楽に書けないかな~って調べてたら「構造的パターンマッチング(match/case)」に行きついたので紹介します。
match/case とは
完全に理解したいのであれば、PEP636を読むことをおすすめします。
ドキュメントもありますが、似たようなことを書いてあるのでお好きな方を…
ここでは基本的な使い方をまとめておきます。
例えば、受け取ったコマンドによって処理を決めたい場合などは下記のように書くことが出来ます。
command = input()
match command:
case "Run":
# コマンドとして"Run"を受け取った時の処理。
Run()
case "Stop":
# コマンドとして"Stop"を受け取った時の処理。
Stop()
case _:
# どのcaseにも一致しなかったときの処理。
raise ValueError("そのコマンドは無効です")
-
match command
で、command
に格納されている値を取得します。 -
case
を上から順に見ていき、1で取得した値と一致しているかを判定します。 - パターンに一致した場合は、そのcaseブロックに記載された処理を行い、match文を抜けています。
- どのcaseにも一致していなければ、
case _
ブロックの処理が実行されます1。
C言語などのswitch
に似ていますが、大きな違いはcase
の最後にbreak
は不要なところですかね。
型でmatch/case
match case
を一通り見たとき、最初に示したコードは下記のように書けるんじゃないか?と思いました。
number: Optional[int | str | float] = GetHoge()
match number:
case int:
print("Integer!!!")
case str:
print("String!!!")
case float:
print("float!!!")
case None:
print("None!!!")
case _:
raise TypeError
しかし、これはSyntax Error
になってしまいます。
case
の後のint
,str
,float
はキャプチャパターンとして扱われてしまうためです(参考)
→ 型とは別の扱いになってしまう2
では、どのように指定するのかと言うと…
number: Optional[int | str | float] = GetHoge()
match number:
case int():
print("Integer!!!")
case str():
print("String!!!")
case float():
print("float!!!")
case None:
print("None!!!")
case _:
raise TypeError
のように書きます。これはクラスパターンと呼ばれるものです(参考)
補足
Type Guard
嬉しいことにcase
ブロック直下のnumber
は、case
に応じて型チェッカーが働いてくれます。
自作クラスの場合のクラスパターン
中身の要素が異なるときに、下記のようなクラスパターンを書くとどうなるでしょうか?
@dataclass
class User:
name: str = "hoge"
def main():
my_user = User("fuga")
match my_user:
case User():
print("User")
case _:
print("other")
答えはcase User
が評価されて、User
が出力されます。
これは、クラスパターンの引数が存在しない場合はisinctance(my_user, User)
と同義だからです3。
属性の一致も見たいのであれば、case User()
の引数にチェックする属性を入れてあげましょう。
def main():
my_user = User("fuga")
match my_user:
case User("hoge"):
print("User")
case _:
print("other")
この場合はother
が出力されます。
終わりに
match case
、意外と難しいと感じました…(特に構文が厄介)
でも毎回評価に用いる変数を書かなくていいのはめちゃくちゃ好きです。
ちなみにPython3.10から追加されたので、使用する際にはバージョンを確認してください。