[Python]構造的パターンマッチング

Python

Pythonのバージョン3.10.0でパターンマッチが追加されましたが、このページで使い方をまとめました。

参考ページ
PEP 636 -- Structural Pattern Matching: Tutorial

私の環境
WSL2 - Ubuntu 20.04 LTS
Python 3.10.0

 Androidアプリを作成しました。
 感情用のメモ帳です。

スポンサーリンク
スポンサーリンク

基本的な文型

文型は次のような形です。

match 対象:
    case パターン:
        実行文

対象に、比較したいもの(変数や属性など)を置きます。

パターンには、対象と照合したいリテラル(3とか'python'、[4, 66]など実際の値)を置きます。
変数を置いた場合は代入されるのですが、後述します。

対象とパターンが等しいなら実行文が遂行されます。

実際に当てはめてみましょう。

Python 3.10.0
Type "help", "copyright", "credits" or "license" for more information.
>>> language = 'Python'
>>> match language:
...     case 'Ruby':
...         print('Rubyです!')
...     case 'Java':
...         print('Javaです!')
...     case 'Python':
...         print('Pythonです!')
...     case 'C#':
...         print('C#です!')
... 
Pythonです!

上記はlanguageが'Python'と一致したので、一致した箇所の実行文が遂行されています。

caseは上から順に見ていき、一致した場合にそれ以降のcaseは検証されません。

ワイルドカード

パターンに一致するものがなかったときは何も実行されません。

もしどれにも一致しなかったときに実行したいことがある場合、
パターンのところに「_」(アンダースコア)を置きます。

アンダースコアは何にでもマッチするワイルドカードとして機能します。

>>> language = 'JavaScript'
>>> match language:
...     case 'Ruby':
...         print('Rubyです!')
...     case 'Java':
...         print('Javaです!')
...     case 'Python':
...         print('Pythonです!')
...     case _:
...         print('該当するものがありません')
... 
該当するものがありません

キャプチャーパターン

パターンに変数を置くと、その変数に対象のリテラルを代入します。

>>> language = 'Python'
>>> match language:
...     case test:
...         print(test)
... 
Python

変数testにlanguageの値'Python'が代入されます。
上記は実行文が必ず遂行されます。

実行文で対象の値を使いたいときに変数を置きます。

>>> language = ['Python', 3]
>>> match language:
...     case ['Ruby', years]:
...         print(f'あなたはRubyを{years}年勉強しています')
...     case ['Python', years]:
...         print(f'あなたはPythonを{years}年勉強しています')
... 
あなたはPythonを3年勉強しています

caseでリストの1番目を'Python'と照合しますが、リストの2番目は数値の3がyearsに代入されます。

変数を置くとパターンと一致していることになるので、ワイルドカードと似ていますが、ワイルドカードは値(リテラル)を代入しません。

>>> match language:
...     case ['Python', _]:
...         print(f'あなたはPyhonを{_}年勉強しています')
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
NameError: name '_' is not defined

リストとしてパターンを取得したいなら、*変数でキャッチできます。

>>> languages = ['Python', 'Ruby', 'Java', 'C#']
>>> match languages:
...     case ['Python', *lang]:
...         print(lang)
... 
['Ruby', 'Java', 'C#']

パターンと照合した上で、変数に代入したいときは「as」を使います。

>>> language = ['Python', 3]
>>> match language:
...     case ['Ruby' as lang, 3]:
...         print(f'あなたは{lang}を3年勉強しています')
...     case ['Python' as lang, 3]:
...         print(f'あなたは{lang}を3年勉強しています')
... 
あなたはPythonを3年勉強しています

リストの一番目を照合し、中身を変数langに代入しています。

タプル・リストとのマッチング

個人的に一番便利だなと思ったのが、タプルやリストのような複数のリテラルを持つものとのマッチングです。

>>> sports = ('ボール', '投げる', 'シュート')
>>> match sports:
...     case ('ボール', '蹴る', 'シュート'):
...         print('それはサッカーですね')
...     case ('ボール', '打つ', 'ヒット'):
...         print('それは野球ですね')
...     case ('ボール', '打つ', 'スマッシュ'):
...         print('それはテニスですね')
...     case ('ボール', '投げる', 'シュート'):
...         print('それはバスケットですね')
... 
それはバスケットですね

上記は、対象が'ボール''投げる''シュート'なので、3つすべてと合致したときに実行されます。

if文を使うよりもだいぶスッキリとした書き方ですよね。

またif文では、シーケンスの長さが異なるとインデックスエラーが出てしまいますが、match文では問題ありません。

sports = ['シャトル']

# if文
>>> if sports[0] == 'シャトル' and \
...    sports[1] == '打つ':
...     print('それはバドミントンですね')
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IndexError: list index out of range

# match文
>>> match sports:
...     case ['シャトル', '打つ']:
...         print('それはバドミントンですね')
... 
>>>

辞書とのマッチング

辞書とのマッチングでは、リストやタプルとは違い、辞書の中身すべてが一致しなくても構いません。
キーと値のセットがマッチしたときに実行されます。

>>> basket = {'milk': 2, 'egg': 5, 'apple': 1}
>>> match basket:
...     case {'milk': 5}:
...         print('カゴに牛乳が5個入っています')
...     case {'milk': 2}:
...         print('カゴに牛乳が2個入っています')
...     case {'egg': 5}:
...         print('カゴに卵が5個入っています')
...     case {'apple': 1}:
...         print('カゴにりんごが1個入っています')
... 
カゴに牛乳が2個入っています

2つ目のケースとマッチしたため、3つ目以降は無視されています。

注意点としてキーにはリテラルしか使用できません。

>>> fruit = 'apple'
>>> basket = {fruit: 5}
>>> basket
{'apple': 5}
>>> match basket:
...     case {fruit: _}:
  File "<stdin>", line 2
    case {fruit: _}:
               ^
SyntaxError: invalid syntax

パターンのキーに変数を使用したためエラーが出ました。
キーにワイルドカードを使っても同様のエラーになります。

条件を追加する

|(バーティカルバー)を使うと、排他的論理和(または)、

if(ガード)を使って条件の追加ができます。

# または
>>> basket = ('paper', 'apple', 'banana', 'coffee')
>>> for item in basket:
...     match item:
...         case 'apple' | 'banana':
...             print(f'{item}は果物です')
...         case _:
...             print(f'{item}は果物じゃないです')
... 
paperは果物じゃないです
appleは果物です
bananaは果物です
coffeeは果物じゃないです

# if
>>> basket = {'apple': 10}
>>> match basket:
...     case {'apple': num} if num < 10:
...         print('りんごは10個未満です')
...     case {'apple': num} if num >= 10:
...         print('りんごが10個以上あります')
... 
りんごが10個以上あります

ガード(if)はパターンをチェックしたあとに検証されます。
パターンと合致しなかった場合には検証されません。

まとめ

構造的パターンマッチングについて見てきました。

# 基本文型
match 対象:
    case パターン:
        実行文

ワイルドカードとして機能する「_」

パターンに変数を置くと代入される

「|」や「if」で条件の追加

このページが少しでもお役に立てたのなら幸いです。

タイトルとURLをコピーしました