match-case【match文】3.10

メモ ( 概要 ソフトキーワード ) 構文 ( 各種パターン )

メモ

概要

  • C言語系の switch 文の拡張
    • 整数値だけでなく、各種型と比較可能
    • 複数パターン(or)の指定可 (case の連続相当)
    • break 等はなし (後のパターンの処理はなし)
    • default: 相当は、case _: (アンダースコア)
  • 他言語のパターンマッチング文相当

ソフトキーワード

  • matchcase は、ソフトキーワード
    • match-case として使用される場合のみキーワード、その他では自由に使用可
  • ワイルドカード パターン での _ (アンダースコア)もソフトキーワード

構文

match マッチ対象式:
case パターン1 [if ガード条件1]:処理1 (1行)
処理1 (複数行可)
・・・
case パターンn [if ガード条件n]:処理n (1行)
処理n (複数行可)
[case _:default 処理 (1行)
default 処理 (複数行可)]


マッチ対象式マッチ対象
カンマ区切り:tuple【タプル型】となるので、パターンnも合わせる
パターンn各種パターン の指定
複数 (OR パターン)サブパターン1|・・・|サブパターンn
(キャプチャ変数:サブパターン内では重複不可・サブパターン間では同じ指定が必要)
キャプチャサブパターンn as キャプチャ名n
ガード条件n
パターンn全体に対する制約
処理n (1行)処理n (複数行可)
パターンnにマッチする場合の処理、どちらか1つを指定
default 処理 (1行)default 処理 (複数行可)
どのパターンにもマッチしない場合の処理、どちらか1つを指定 (キャプチャは不可)

※:正式な構文は、The match statement を参照

各種パターン

(サブ)パターンn備考
リテラル パターン〔
符号付き数値
複素数
文字列f-string【フォーマット済み文字列リテラル】は不可
None
True
False
キャプチャ パターン〔
変数
(ワイルドカードのみを除く)
変数に値をバインド
ワイルドカード パターン〔
( _ はソフトキーワード:パターン内でのみキーワード)
_ (アンダースコア) を含むパターン 該当位置と常にマッチ
(参照不可)
_ (アンダースコア) のみ 常にマッチ (構文にも記述)
(最後にのみ指定可・参照不可)
値 パターン〔
.name 名前付き定数
(列挙型等)
グループ パターン
(パターン) カッコで強調させるだけ
シーケンス パターン〔
(A) [要素 (複数:カンマ区切り)]
(B) (要素 (複数:カンマ区切り))
(C) 要素 (カンマ区切り)

(タプル:1要素でもカンマが必要)
(A) list【リスト型】
(B) tuple【タプル型】
(C) タプル型のカッコ省略形

要素
*でアンパック相当 (アンパック:各型の詳細参照)
| で or 指定可
as でキャプチャ可
マッピング パターン〔
{キー1:値1, ・・・[, **キャプチャ変数]} dict【辞書型】
指定キー以外は無視
重複キーはエラー
** で残り部分のキャプチャ可 (最後にのみ指定可 / **_は不可)
クラス パターン〔
クラス名([位置引数 (カンマ区切り)][,][キーワード引数 (カンマ区切り)])
コンストラクタの記述相当
型チェック
キャプチャ可
__match_args__ 属性 3.10 指定で位置引数が可能
マッピング パターン 内での使用も可

基本

def func_match(subject):
    match subject:
        case 1:
            # 単独
            print('(int-A) 1')
        case 3 | 4 | 5:
            # 複数
            print('(int-B) (3 | 4 | 5)')
        case 7 | 8 | 9 as x:
            # 複数+キャプチャ
            print(f'(int-C) {x} (7 | 8 | 9)')

        case 'string':
            # 単独 (文字列)
            print('(str)')

        case [2, 3, 4]:
            # list
            print('(list-A) ([2, 3, 4])')
        case [x, y, z] as full if x < y < z:
            # list+キャプチャ+ガード
            print(f'(list-B) [{x}, {y}, {z}] {full} (x < y < z)')
        case [x, y, z] as full:
            # list+キャプチャ
            print(f'(list-C) [{x}, {y}, {z}] {full}')

        case x if x < 0:
            # キャプチャ+ガード
            print(f'(int) {x} (x < 0)')

        case _:
            print('(Z) その他')


func_match(1)  # 出力:(int-A) 1
func_match(4)  # 出力:(int-B) (3 | 4 | 5)
func_match(8)  # 出力:(int-C) 8 (7 | 8 | 9)
func_match('string')  # 出力:(str)

func_match([2, 3, 4])
# 出力:(list-A) ([2, 3, 4])
func_match([12, 34, 56])
# 出力:(list-B) [12, 34, 56] [12, 34, 56] (x < y < z)
func_match([12, 345, 67])
# 出力:(list-C) [12, 345, 67] [12, 345, 67]

func_match(-9)  # 出力:(int) -9 (x < 0)
func_match(10)  # 出力:(Z) その他

ソフトキーワード

def match_softkeyword(subject, match=None, case=None):
    match subject:
        case 1:
            print(f'(A) 1 {match = } {case = }')
            match = 123
            case = 456
            print(f'{match = } {case = }')
        case 2:
            print('(B) 2')
        case _:
            print('(Z) その他')


match_softkeyword(1, 23, 45)
# 出力:(A) 1 match = 23 case = 45
# 出力:match = 123 case = 456
match_softkeyword(2)
# 出力:(B) 2
match_softkeyword(3)
# 出力:(Z) その他

match = 456
case = 789
print(f'{match = } {case = }')
# 出力:match = 456 case = 789

リテラル パターン

def match_literal(subject):
    match subject:
        case 10:
            print('(int-A) [10]')
        #case (12 * 34):  # SyntaxError
        #    pass
        case 20:
            print('(int-B) [20]')
        case 30 | 40 | 50:
            # 複数指定
            print('(int-C) [30 | 40 | 50]')
        case 60 | 70 | 80 as n:
            # 複数指定+
            print(f'(int-D) {n} [60 | 70 | 80]')

        case -123.456 as f:
            print(f'(float-A) {f}')

        case 1 + 2j:
            print('(complex-A) [1 + 2j]')
        case -3 - 4j as c:
            print(f'(complex-B) {c}')

        case 'string':
            print('(str-A) [string]')
        case '''String''' as s:
            print(f'(str-B) {s}')
        case r'STRING' as s:
            print(f'(str-C) {s}')
        #case f'STRING':  # SyntaxError
        #    pass

        case None:
            print('(None)')

        case True as b:
            print(f'(bool) {b}')
        case False as b:
            print(f'(bool) {b}')

        case n if n % 9 == 0:
            print(f'(guard) {n} [9xN]')

        case _:
            print('(Z) その他')


match_literal(10)  # 出力:(int-A) [10]
match_literal(20)  # 出力:(int-B) [20]
match_literal(40)  # 出力:(int-C) [30 | 40 | 50]
match_literal(70)  # 出力:(int-D) 70 [60 | 70 | 80]
match_literal(-123.456)  # 出力:(float-E) -123.456
match_literal(-123)  # 出力:(Z) その他

match_literal(1 + 2j)  # 出力:(complex-A) [1 + 2j]
match_literal(-3 - 4j)  # 出力:(complex-B) (-3-4j)

match_literal('string')  # 出力:(str-A) [string]
match_literal('String')  # 出力:(str-B) String
match_literal('''String''')  # 出力:(str-B) String
match_literal('STRING')  # 出力:(str-C) STRING
match_literal(r'STRING')  # 出力:(str-C) STRING

match_literal(None)  # 出力:(None)

match_literal(True)  # 出力:(bool) True
match_literal(False)  # 出力:(bool) False

match_literal(18)  # 出力:(guard) 18 [9xN]
match_literal(27)  # 出力:(guard) 27 [9xN]

match_literal(100)  # 出力:(Z) その他

キャプチャ パターン

def match_capture(subject):
    match subject:
        case [x, 0]:
            print(f'(A) [{x}, 0] ([x, 0])')
        case [0, y]:
            print(f'(B) [0, {y}] ([0, y])')
        case [_, 100] | [100, _] as lst:
            print(f'(C) {lst} ([_, 100] | [100, _])')
        case [x, y] if x < y:
            print(f'(D) [{x}, {y}] (x < y)')
        case [x, y]:
            print(f'(E) [{x}, {y}]')

        case x:
            print(f'(Z) {x}')


match_capture([123, 0])  # 出力:(A) [123, 0] ([x, 0])
match_capture([0, 456])  # 出力:(B) [0, 456] ([0, y])
match_capture([123, 100])  # 出力:(C) [123, 100] ([_, 100] | [100, _])
match_capture([100, 456])  # 出力:(C) [100, 456] ([_, 100] | [100, _])
match_capture([123, 456])  # 出力:(D) [123, 456] (x < y)
match_capture([123, -456])  # 出力:(E) [123, -456]

match_capture(123)  # 出力:(Z) 123
match_capture('string')  # 出力:(Z) string

ワイルドカード パターン

def match_wildcard(subject):
    match subject:
        case [x, _, 0]:
            print(f'(A) [{x}, _, 0] ([x, _, 0])')
        case [123, _, _]:
            print(f'(B) ([123, _, _])')
        case [1, _, _] as lst:
            print(f'(C) {lst} ([1, _, _])')
        #case [_, y, z] if _ < 0:  # UnboundLocalError
        #    pass
        case [x, y, _] if x < 0:
            #print(_)  # UnboundLocalError
            print(f'(D) [{x}, {y}, _] ([x, y, _] if x < 0)')
            _ = '変数D'
            print(_)
        case _:
            print('(Z) その他')


match_wildcard([123, 456, 0])  # 出力:(A) [123, _, 0] ([x, _, 0])
match_wildcard([123, 456, 789])  # 出力:(B) ([123, _, _])
match_wildcard([1, 23, 456])  # 出力:(C) [1, 23, 456] ([1, _, _])
match_wildcard([-123, 456, 789])
# 出力:(D) [-123, 456, _] ([x, y, _] if x < 0)
# 出力:変数D
match_wildcard([100, 456, 789])
# 出力:(Z) その他

_ = '変数'
print(_)
# 出力:変数

値 パターン

from enum import Enum

class Color(Enum):
    RED = 0xFF0000
    GREEN = 0x00FF00
    BLUE = 0x0000FF
    BLACK = 0x000000
    WHITE = 0xFFFFFF
    GRAY = 0x808080


def match_enum(subject):
    match subject:
        case Color.RED:
            print('Red')
        case Color.GREEN:
            print('Green')
        case Color.BLUE:
            print('Blue')
        case Color.WHITE | Color.BLACK:
            print('Black or White')
        case _:
            print('その他')


match_enum(Color.RED)    # 出力:Red
match_enum(Color.GREEN)  # 出力:Green
match_enum(Color.BLUE)   # 出力:Blue
match_enum(Color.BLACK)  # 出力:Black or White
match_enum(Color.WHITE)  # 出力:Black or White
match_enum(Color.GRAY)   # 出力:その他

シーケンス パターン

def match_sequence(subject):
    match subject:
        case [123, 456, 789] as seq:
            print(f'(A) {seq} [123, 456, 789]')
        case (123, 456, 789):  # 上記 case と同等
            print('(B) (123, 456, 789)')
        case 123, 456, 789:    # 上記 case と同等
            print('(C) 123, 456, 789')

        case [1, *yz]:
            # アンパック相当
            print(f'(D) [1, {yz}]')
        case [123, 1 | 2 | 3, 789]:
            # 複数要素
            print('(E) [123, 1 | 2 | 3, 789]')
        case [123, 4 | 5 | 6 as y, 789]:
            # 要素キャプチャ
            print(f'(F) [123, {y} (4 | 5 | 6), 789]')
        case [x, y, z] if x < y < z:
            # ガード条件
            print(f'(G) [{x}, {y}, {z}] (x < y < z)')

        case _:
            print('その他')


lst = [123, 456, 789]
match_sequence(lst)  # 出力:(A) [123, 456, 789] [123, 456, 789]
tpl1 = (123, 456, 789)
match_sequence(tpl1)  # 出力:(A) (123, 456, 789) [123, 456, 789]
tpl2 = 123, 456, 789
match_sequence(tpl2)  # 出力:(A) (123, 456, 789) [123, 456, 789]

match_sequence([1, 456, 789])  # 出力:(D) [1, [456, 789]]
match_sequence([123, 2, 789])  # 出力:(E) [123, 1 | 2 | 3, 789]
match_sequence([123, 5, 789])  # 出力:(F) [123, 5 (4 | 5 | 6), 789]

match_sequence([12, 34, 56])  # 出力:(G) [12, 34, 56] (x < y < z)
match_sequence([12, 345, 67])  # 出力:その他

マッピング パターン

def match_mapping(subject):
    match subject:
        case {'key1': 'V1', 'key2': 'V2'}:
            print('(A)')
        case {'key1': 'V01', 'key2': 'V02'} | {'key1': 'V11', 'key2': 'V12'}:
            print('(B)')
        case {'key1': 'V21', 'key2': 'V22'} | {'key1': 'V31', 'key2': 'V32'} as dic:
            print(f'(C) {dic}')
        case {'key1': v1, 'key2': v2} if int(v1) < int(v2):
            print(f'(D) {v1} {v2}')

        #case {'key1': 'V1', 'key1': 'V01'}:  # SyntaxError (同一キー)
        #    pass

        #case {'keyA': 'VA', 'keyB': 'VB', **_}:  # SyntaxError (**_)
        #    pass
        case {'keyA': 'VA', 'keyB': 'VB', **dic} as full:
            print(f'(E) {dic}')
            print(f'    {full}')

        case {'keyX': vx, 'keyY': vy} as full if vx < vy:
            print(f'(X) {full} (vx < vy)')

        case _:
            print('(Z) その他')


match_mapping({'key1': 'V1', 'key2': 'V2'})  # 出力:(A)
match_mapping({'key1': 'V1', 'key2': 'V2', 'key3': 'V3'})  # 出力:(A)
match_mapping({'key1': 'V01', 'key2': 'V02'})  # 出力:(B)
match_mapping({'key1': 'V11', 'key2': 'V12'})  # 出力:(B)
match_mapping({'key1': 'V21', 'key2': 'V22'})
# 出力:(C) {'key1': 'V21', 'key2': 'V22'}
match_mapping({'key1': 'V31', 'key2': 'V32'})
# 出力:(C) {'key1': 'V31', 'key2': 'V32'}
match_mapping({'key1': '123', 'key2': '456'})
# 出力:(D) 123 456

match_mapping({'keyA': 'VA', 'keyB': 'VB'})
# 出力:(E) {}
# 出力:    {'keyA': 'VA', 'keyB': 'VB'}
match_mapping({'keyA': 'VA', 'keyB': 'VB', 'keyC': 'VC', 'keyD': 'VD'})
# 出力:(E) {'keyC': 'VC', 'keyD': 'VD'}
# 出力:    {'keyA': 'VA', 'keyB': 'VB', 'keyC': 'VC', 'keyD': 'VD'}

match_mapping({'keyX': 123, 'keyY': 456})
# 出力:(X) {'keyX': 123, 'keyY': 456} (vx < vy)
match_mapping({'keyX': 123, 'keyY': 45})
# 出力:(Z) その他

match_mapping({'key9': 'V9'})  # 出力:(Z) その他

クラス パターン

class MyClass:
    def __init__(self, name):
            self._name = name


class MyClass2:
    __match_args__ = ('_name',)
    def __init__(self, name):
        self._name = name


def match_class(subject):
    match subject:
        case int(100):
            print(f'(int-A) [100]')
        case int(200 | 300) as n:
            print(f'(int-B) {n} [200 | 300]')
        case int(n) if n < 0:
            print(f'(int-C) {n} (n < 0)')
        case int(n):
            print(f'(int-D) {n}')

        case float(f):
            print(f'(float) {f}')

        case str(s) if len(s) < 5:
            print(f'(str-A) {s} (len(s) < 5)')
        case str(s):
            print(f'(str-B) {s}')

        case MyClass(_name='name'):
            print('(MyClass-A) [name]')
        case MyClass(_name='NAME'):
            print('(MyClass-B) [NAME]')

        case MyClass2('name'):
            print('(MyClass2-A) [name]')
        case MyClass2('NAME'):
            print('(MyClass2-B) [NAME]')
        case MyClass2(x):
            print(f'(MyClass2-C) {x}')

        case {"text": str(msg), "color": str(c)}:
            print(f'(dict-A) {msg=} {c=}')
        case {"text": str(msg), "color": int(c)} if c < 0x80:
            print(f'(dict-B) {msg=} {c=} (c < 0x80)')
        case {"text": str(msg), "color": int(c)}:
            print(f'(dict-C) {msg=} {c=}')

        case {"text": MyClass2('name'), "color": str(c)}:
            print(f'(dict-D) {c=} [name]')
        case {"text": MyClass2('NAME'), "color": str(c)}:
            print(f'(dict-E) {c=} [NAME]')
        case {"text": MyClass2(name), "color": str(c)}:
            print(f'(dict-F) {name=} {c=}')

        case _:
            print('(Z) その他')


match_class(100)  # 出力:(int-A) [100]
match_class(200)  # 出力:(int-B) 200 [200 | 300]
match_class(300)  # 出力:(int-B) 300 [200 | 300]
match_class(-123)  # 出力:(int-C) -123 (n < 0)
match_class(123)  # 出力:(int-D) 123

match_class(float(123))  # 出力:(float) 123.0

match_class('str')  # 出力:(str-A) str (len(s) < 5)
match_class('string')  # 出力:(str-B) string

match_class(MyClass('name'))  # 出力:(MyClass-A) [name]
match_class(MyClass('NAME'))  # 出力:(MyClass-B) [NAME]
match_class(MyClass('name_x'))  # 出力:(Z) その他

match_class(MyClass2('name'))  # 出力:(MyClass2-A) [name]
match_class(MyClass2('NAME'))  # 出力:(MyClass2-B) [NAME]
match_class(MyClass2('name_x'))  # 出力:(MyClass2-C) name_x

match_class({'text': 'MSG', 'color': 'RED'})
# 出力:(dict-A) msg='MSG' c='RED'
match_class({'text': 'MSG', 'color': 0})
# 出力:(dict-B) msg='MSG' c=0 (c < 0x80)
match_class({'text': 'MSG', 'color': 0xFF})
# 出力:(dict-C) msg='MSG' c=255

match_class({'text': MyClass2('name'), 'color': 'RED'})
# 出力:(dict-D) c='RED' [name]
match_class({'text': MyClass2('NAME'), 'color': 'GREEN'})
# 出力:(dict-E) c='GREEN' [NAME]
match_class({'text': MyClass2('name_x'), 'color': 'BLUE'})
# 出力:(dict-F) name='name_x' c='BLUE'
match_class({'text': MyClass2('name'), 'color': 0xFF})
# 出力:(Z) その他