[解決!Python]match文のシーケンスパターンで複雑な条件を記述するには解決!Python

構造的パターンマッチのシーケンスパターンではさまざまなサブパターンを駆使することで柔軟な条件分岐が可能だ。その例と注意点を紹介する。

» 2023年07月11日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「解決!Python」のインデックス

連載目次

# シーケンスパターン内でリテラルパターンとキャプチャーパターンを使用
cmd = 'move to (123, 456)'
cmd = cmd.replace('(', '').replace(')', '').replace(',', '').split()
print(cmd)  # ['move', 'to', '123', '456']

match cmd:
    case ['move', 'to', x, y]:
        print(f'moved to ({x}, {y}).')
    case _:
        print('not matched.')

# シーケンスパターン内でリテラルパターンとスターパターンを使用
cmd = 'move to (123, 456)'.split()
match cmd:
    case ('move', 'to', *rest):
        for idx, item in enumerate(rest):
            tmp = item.replace('(', '').replace(')', '').replace(',', '')
            rest[idx] = tmp
        print(f'moved to ({rest[0]}, {rest[1]}).')
    case _:
        print('not matched.')

# シーケンスパターン内ではスターパターンの後にもキャプチャーパターンを書ける
cmd0 = 'get gun bullet shield sword'.split()
cmd1 = 'get gun bullet shield and sword'.split()
cmds = [cmd0, cmd1]

for cmd in cmds:
    match cmd:
        case ['get', *items]:
            print(f'got {" ".join(items)}')
        case ['get', *items, 'and', rest]:
            print(f'got {" ".join(items)}, plus {rest}')

# ただし、特殊なものを先に書く必要がある
for cmd in cmds:
    match cmd:
        case ['get', *items, 'and', rest]:
            print(f'got {" ".join(items)}, plus {rest}')
        case ['get', *items]:
            print(f'got {" ".join(items)}')

# シーケンスパターン内でワイルドカードパターンも書ける
match cmd0:  # 比較対象は['get', 'gun', 'bullet', 'shield', 'sword']
    case ['get', *items, _]:  # 'sword'が_にマッチする
        print(f'got {" ".join(items)}')

match cmd0:  # 比較対象は['get', 'gun', 'bullet', 'shield', 'sword']
    case ['get', *_, 'sword']:  #  ワイルドカードパターンに*を付加することも可能
        print('got sword and something')


シーケンスパターン

 Python 3.10で導入されたmatch文を使うと条件分岐をより強力、より柔軟に行える。その中でもシーケンスパターンはリストなどがどのような値を含んでいるかに応じて処理を分岐させるのに便利に使える。

 シーケンスパターンに限らず、あるパターンの部分となるパターンのことを「サブパターン」と呼ぶ。以下はサブパターンとして、リテラルパターン(リテラルとの比較)とキャプチャーパターン(比較対象の値を変数に代入する)とを使用する例だ。

cmd = 'move to (123, 456)'
cmd = cmd.replace('(', '').replace(')', '').replace(',', '').split()
print(cmd)  # ['move', 'to', '123', '456']

match cmd:
    case ['move', 'to', x, y]:
        print(f'moved to ({x}, {y}).')
    case _:
        print('not matched.')


 この例では'move to (123, 456)'という文字列はかっこ「()」とカンマ「,」の削除が行われた後、空白文字を区切りとして分割され、['move', 'to', '123', '456']というリスト(シーケンス)に変換されている。

 先頭の「case ['move', 'to', x, y]:」では、上記のリストが「['move', 'to', x, y]」というシーケンスパターンにマッチするかどうかを調べている。リストの内容と見比べてみると、最初の'move'と次の'to'はリテラルパターン(シーケンスパターンのサブパターン)でのマッチングであり、これはマッチする。残りの「x, y」という部分はキャプチャーパターンであり、リストに含まれている2つの要素「'123', '456'」がそれぞれ変数xとyにキャプチャーされる。

 よって、このマッチングは成功し「moved to (123, 456).」と出力される。リストcmdの内容が「['move', 'to', '123']」だと変数yにキャプチャーされる要素がないので、マッチせずに「not matched.」と表示される。

 シーケンスパターンでは、可変個の要素を扱うために「*」付きの変数も使用できる(これをPEP 634では「star_pattern」と表記しているので、ここでも「スターパターン」と呼ぶ)。これは関数やメソッドの可変長引数と同様なものだ。以下に例を示す。

cmd = 'move to (123, 456)'.split()
match cmd:
    case ('move', 'to', *rest):
        for idx, item in enumerate(rest):
            tmp = item.replace('(', '').replace(')', '').replace(',', '')
            rest[idx] = tmp
        print(f'moved to ({rest[0]}, {rest[1]}).')
    case _:
        print('not matched.')


 この例では、2つの変数xとyを使ったキャプチャーパターンではなく、*付きの変数restに2つの値をキャプチャーしようとしている。リストcmdの要素である'(123,'と'456)'はrestにキャプチャーされ['(123,', '456)']というリストが形成される。その後、このリストを反復して、文字列からかっことカンマが削除されたものがrestの対応する要素にセットされるようにしている(そのため、enumerate関数でリストのインデックスと要素を取り出している)。

 マッチ対象が不特定多数の要素を含んでいる場合には、スターパターンを使うと、特定の意図で集められた不特定多数の要素を1つの変数にまとめて扱えるようになるだろう。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。