LoginSignup
20
4

More than 1 year has passed since last update.

【Python】isinstanceをmatch/caseに置き換える

Last updated at Posted at 2023-03-06

概要

ある日、下記のような(めんどくさい)処理を書いていました。

# 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("そのコマンドは無効です")
  1. match commandで、commandに格納されている値を取得します。
  2. caseを上から順に見ていき、1で取得した値と一致しているかを判定します。
  3. パターンに一致した場合は、そのcaseブロックに記載された処理を行い、match文を抜けています。
  4. どの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に応じて型チェッカーが働いてくれます。

  • matchの横のnumberの型
    image.png
  • case int()ブロック内のnumber
    image.png

自作クラスの場合のクラスパターン

中身の要素が異なるときに、下記のようなクラスパターンを書くとどうなるでしょうか?

@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から追加されたので、使用する際にはバージョンを確認してください。

  1. ワイルドカードと呼ばれるもので、これは必ず一致する。

  2. Noneは「リテラル パターン」として扱われるので、処理上問題ない(True/Falseも同じ)

  3. PEP634のIf no arguments are present, the pattern succeeds if the check succeeds. Otherwise:isinstance()より

20
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
4