[解決!Python]match文で構造的パターンマッチを行うには解決!Python

Python 3.10で追加されたmatch文は条件分岐をより柔軟形で行える。その基本構文と記述可能なパターンをざっくりと紹介する。

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

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

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

連載目次

# 基本型
match subject_expr:
    case pattern0 [if ...]:
        # subject_exprがpattern0にマッチした(かつ、if句がTrueの)ときに行う処理
        pass
    case pattern1 [if ...]:
        # subject_exprがpattern1にマッチした(かつ、if句がTrueの)ときに行う処理
        pass
    # 省略
    case patternN [if ...]:
        # subject_exprがpatternNにマッチした(かつ、if句がTrueの)ときに行う処理
        pass
    case _:  # 全てにマッチする
        # 上のパターンマッチが全て失敗したときにはワイルドカードパターンが成功する
        # その場合に行う処理をワイルドカードパターンのブロックに記述する
        pass

# リテラルパターン
values = [100, '100', True]

for value in values:
    match value:
        case 100:
            print('int:', value)
        case True:
            print('bool:', value)

# ガード(if句)
somedata = 2
values = [100, '100', True]

for value in values:
    match value:
        case 100 if somedata == 0:
            print('int:', value)
        case '100':
            print('str:', value)
        case True if somedata == 2:
            print('bool:', value)

# キャプチャーパターン
values = [100, 200, 300]
for value in values:
    match value:
        case 100:
            print('100')
        case 200:
            print('200')
        case captured:  # キャプチャーパターンは最後に書く(必ず成功)
            print(captured)

# ワイルドカードパターン
values = [100, '100', True]

for value in values:
    match value:
        case 100:
            print('int:', value)
        case True:
            print('bool:', value)
        case _:  # ワイルドカードパターンは最後に書く(必ず成功)
            print(f'matched to wildcard: {value}')

# ORパターン
values0 = [True, False, True, False]
values1 = [True, True, False, False]

for v0, v1 in zip(values0, values1):
    match (v0, v1):
        case (True, True) | (False, False):
            print('2 items have same values')
            print(v0, v1)
        case (True, False) | (False, True):
            print('2 items have not same values')
            print(v0, v1)
            
# ASパターン
values = ['I do something', 'I do anything']

for value in values:
    vs = value.split()
    match vs:
        case ['I' as s, 'do' as v, ('something' | 'anything') as o]:
            print(f'S: {s}, V: {v}, O: {o}')

# バリューパターン(Value Pattern)
class Foo:
    BAR = 100
    BAZ = 200

values = [0, 100, 200, 300]
for value in values:
    match value:
        case Foo.BAR:
            print(f'{value} matched to Foo.BAR({Foo.BAR})')
        case Foo.BAZ:
            print(f'{values} matched to Foo.BAZ({Foo.BAZ})')
        case _:
            print(f'{value} not matched')

# シーケンスパターン
mylists = ['I like Blue', 'I like yellow', 'I like white and red']
mylists = [s.split() for s in mylists]
print(mylists) 
# mylistsの内容
# [['I', 'like', 'Blue'], ['I', 'like', 'yellow'], ['I', 'like', 'white', 'and', 'red']]

for l in mylists:
    match l:
        case ['I', 'like', ('red' | 'green' | 'yellow') as color]:
            print('in 1st case')
            print(f'you like {color}')
        case ['I', 'like', color]:
            print('in 2nd case')
            print(f'you like {color}')
        case ['I', 'like', *other]:
            print('in 3rd case')
            print(other)
            colors = ' '.join(other)
            print(f'you like {colors}')

# マッピングパターン
d0 = {'foo': 'FOO', 'bar': 'BAR'}
d1 = {'hoge': 'HOGE', 'hogehoge': 'HOGEHOGE'}
d2 = {'foo': 'FOO', 'bar': 'BAR', 'baz': 'BAZ'}
mydicts = [d0, d1, d2]

for d in mydicts:
    match d:
        case {'foo': 'FOO', 'bar': value, **items}:
            print(value)
            print(items)
        case {**items}:
            print(items)

# クラスパターン
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

points = [Point(0, 0), Point(1, 1), Point(2, 2)]

for p in points:
    match p:
        case Point(x=0, y=0):
            print('origin')
        case Point(1, 1):
            print(f'({p.x}, {p.y})')
        case _:
            print('not matched')


match文と構造的パターンマッチ

 Python 3.10からは構造的パターンマッチと呼ばれる条件分岐を行うためにmatch文が追加されている。なお、構造的パターンマッチの詳細についてはPEP 634PEP 636を参照のこと。

 match文の基本的な構文は次のようになる。

match subject_expr:
    case pattern0 [if ...]:
        # subject_exprがpattern0にマッチした(かつ、if句がTrueの)ときに行う処理
        pass
    case pattern1 [if ...]:
        # subject_exprがpattern1にマッチした(かつ、if句がTrueの)ときに行う処理
        pass
    # 省略
    case patternN [if ...]:
        # subject_exprがpatternNにマッチした(かつ、if句がTrueの)ときに行う処理
        pass
    case _:  # 全てにマッチする
        # 上のパターンマッチが全て失敗したときにはワイルドカードパターンが成功する
        # その場合に行う処理をワイルドカードパターンのブロックに記述する
        pass


 matchキーワードに続けて記述するのが条件分岐の基となる条件だ(subject_expr。以下では対象式とする)。これを評価した結果が、caseキーワードに続けて記述するパターン(上のpattern0など)にマッチすれば、そのブロックのコードが続けて実行される。マッチしなければ、次のcase節に移動して条件とパターンがマッチするかが調べられる。対象式が全てのパターンにマッチしなければmatch文では実質的には何のコードも実行されない(ただし、上のコードの最後のcase節のように、それまでに指定したパターン全てにマッチしなかった場合に実行するコードを記述することは可能だ)。

 また、case節のパターンにマッチしても、さらに特定の条件が成立しているときにだけコードを実行したい場合がある。このようなときにはパターンに続けてif句を記述する。これをガードと呼ぶ。

 match文では単純なリテラル比較、シーケンスの要素との比較、クラスの属性との比較など、さまざまなパターンを記述できる。また、マッチしたときに特定の値を変数に代入し、ブロック内でその値を使用するといったことも可能だ。

 以下ではmatch文で記述できる各種パターンを簡単に紹介していく。

リテラルパターン

 リテラルパターンは、文字通り、対象式(matchキーワードに続けて記述する式)とcaseキーワードに続けて記述したリテラルとを比較するものだ。以下に例を示す。

values = [100, '100', True]

for value in values:
    match value:
        case 100:
            print('int:', value)
        case True:
            print('bool:', value)
# 出力結果:
#int: 100
#bool: True


 リテラルパターンに記述できるのは数値、文字列、True、False、Noneのみである。上の例では3つの値を要素とするリストの各値(for文の中で変数valueに代入される)をmatch文のcase節で100、Trueと比較している。そのため、文字列の'100'についてはパターンにマッチしない。そのため、上の出力結果を見ると、整数値100とブーリアン値Trueについてのみ出力が行われている。

 マッチはしたがさらに何らかの条件が成立したときにだけ処理を行いたいときにはif句によるガードが使える。以下に例を示す。

somedata = 2
values = [100, '100', True]

for value in values:
    match value:
        case 100 if somedata == 0:
            print('int:', value)
        case '100':
            print('str:', value)
        case True if somedata == 2:
            print('bool:', value)
# 出力結果:
#str: 100
#bool: True


 この例では文字列リテラルとの比較も含めた他、整数値とブーリアン値との比較についてはif句によるガードを記述してある。変数somedataの値は2となっているので、「case 100」でvalueとリテラルパターンがマッチするが、「if somedata == 0」が成立しないのでこのブロックのコードは実行されない。「case True if somedata == 2」はパターンもマッチし、ガードの条件も成立するのでコードが実行される。「case '100':」節についてはガードがないのでこれがマッチすれば、そのブロックのコードが実行される。このため、上の出力結果にあるように2つの出力が得られる。

キャプチャーパターン

 キャプチャーパターンは、対象式の値をPythonの変数に代入する(Pythonの名前と対象式の値を束縛する)パターンだ。このパターンは常に成功するので、他のcase節よりも前に書いてはいけない(最後に書く必要がある)。

values = [100, 200, 300]
for value in values:
    match value:
        case 100:
            print('100')
        case 200:
            print('200')
        case captured:  # キャプチャーパターンは最後に書く(必ず成功)
            print(captured)


 この例では、最初の2つのcase節はリテラルパターンで、整数値100、200との比較を行っている。リストvaluesの要素のうち2つは100と200なので、それらは最初の2つのcase節で処理されるが、300という要素はこれらにはマッチせずに、最後の「case captured:」節で300という値が変数capturedに代入される。代入された値は、そのブロックで使える(この場合は単にループ変数valueを使うだけでもよいが、パターンが複合的になったときにキャプチャーパターンにより対象式の一部の値だけを取り出すといった使い方をすることが多くなるだろう)。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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