検索
連載

Python 3.10の新機能:「構造的パターンマッチ」とはPython最新情報キャッチアップ(2/2 ページ)

Python 3.10で追加された構造的パターンマッチ(match〜case文)の概要と各種パターンの記述方法をサンプルコードと共に紹介する。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
前のページへ |       

グループパターン

 グループパターンは、パターンをかっこ「()」で囲んだもののことだ。後述するORパターンを使って複数のパターンをまとめるときに、関連のあるパターンをかっこで囲むといった使い方が考えられる。

num = 121
match num:
    case (0 | 1) | (120 | 121):  # 「case 0 | 1 | 120 | 12:」と同じ
        print('foo')
    case _:
        print('bar')

グループパターンの使用例

シーケンスパターン

 シーケンスパターンはsubject valueがシーケンスのときにマッチするかどうかを調べる。このとき、シーケンスパターンの要素には上で見たパターン、または後述するパターンを記述できる。

 以下は簡単な例だ。

match 10, 20, 30# (10, 20, 30)というタプルを生成
    case [x]:  # 1要素のシーケンスにマッチして、変数xにその要素を代入
        print(f'1 item: {x}')
    case [x, y]:  # 2要素のシーケンスにマッチして、変数xとyにその要素を代入
        print(f'2 items: {x}, {y}')
    case [0, 10, 20]: # 0、10、20を要素とするシーケンスにマッチ
        print('3 items')
    case [10, x, y]:  # 3要素のシーケンスにマッチし、最後の2つの要素を変数に代入
        print(f'3 items: 10, {x}, {y}')

シーケンスパターンの使用例

 この例ではシーケンスパターンは固定長だ。このときには、subject value(ここでは(10, 20, 30)というタプル)の長さとシーケンスパターンの長さが一致していないとマッチしない(そして、次のcaseブロックに移動する)。そのため、1要素と2要素のシーケンスパターンを記述している最初の2つのcaseブロックはマッチに失敗する。その後の2つのcaseブロックでは、3要素のシーケンスパターンが指定されている。そのため、このいずれかにマッチしてほしいところだ。

 ただし、どちらにマッチするかを見る前に、サブパターンについて話をしておく。サブパターンとはあるパターンを構成するパターンのことだ。シーケンスパターンであれば、シーケンスを構成する個々の要素のことだと考えてよい。あるシーケンスパターンを構成する全てのサブパターンのマッチングが成功すれば、そのシーケンスパターンのマッチングは成功する。一方、サブパターンが1つでもマッチングに失敗すれば、そのシーケンスパターンのマッチングは失敗となる(後述するORパターンでは、サブパターンの1つでも成功すれば、そのパターンのマッチングは成功する)。

 そして、1つ目のシーケンスパターンのサブパターンとは3つのリテラルパターン(0、10、20)のことだ。このサブパターンが左から、subject valueである[10, 20, 30]の先頭要素とそれぞれマッチングされる。0と10、10と20、20と30なので、これらはいずれもマッチングに失敗する。そのため、このcaseブロックは実行されない。

 2つ目のシーケンスパターンは1つのリテラルパターン(10)と、2つのキャプチャーパターン(xとy)であり、先頭要素は10と10でマッチする。その後の2つはキャプチャーパターンなので常に成功して、変数xとyには対応する値が代入される。そのため、このmatch〜case文は「3 items: 10, 20, 30」と出力するはずだ。

 シーケンスパターンには可変長のものもある。可変長のシーケンスパターンとは、そのサブパターンとして、アスタリスク「*」が前置されたキャプチャーパターンかワイルドカードパターンを含むもののことだ。これは関数の可変長引数と似たもので、subject valueであるシーケンスから任意の個数の要素をそのパターンに代入するものだ(ただし、「*_」では代入は行われない)。関数の可変長引数と異なるのは、アスタリスク付きのパターンはシーケンスパターンの任意の位置に置ける点だ。

 以下に可変長のシーケンスパターンの使用例を示す(ここでは話を単純にするためにサブパターンは全てキャプチャーパターンとしてある)。

person = ['isshiki', 'tokyo', 'setagaya', 'kamikitazawa', '03-1111-1111']

match person:
    case (name, *addr, tel):
        print(addr)  # ['tokyo', 'setagaya', 'kamikitazawa']
        print(tel)  # 03-1111-1111

可変長のシーケンスパターンの使用例

 この例では、シーケンスパターンの2つ目の要素(*addr)にアスタリスクがあり、telにはアスタリスクがない(実際、アスタリスク付きのサブパターンを複数記述することは許されていない)。これは元のシーケンスとシーケンスパターンの要素数を考慮して適切にキャプチャーを行ってくれるということだ。

 なお、可変長のシーケンスパターンは、アスタリスク付きのサブパターンは空でもよいが、それよりも前にあるサブパターンには対応する要素がsubject valueに存在していなければマッチングに失敗することは覚えておこう(上のコードでpersonの要素が1つしかなければマッチングは成功するが、変数telは定義されない。また、personが空のシーケンスの場合はマッチングに失敗する)。

 注意点としては、文字列やバイト列はれっきとしたPythonのシーケンスだが、シーケンスパターンではこれらはシーケンスとしては扱われないことが挙げられる。これらはリテラルパターンの値として使用される。

マッピングパターン

 マッピングパターンは辞書のようなキー/値の組を持ったデータに対してマッチングを行う際に使用する。簡単な例を以下に示す。

class Dog:
    def __init__(self, name, role):
        self.name = name
        self.role = role

class Cat:
    def __init__(self, name, skill):
        self.name = name
        self.skill = skill

def getPet(arg):
    match arg:
        case {'type': 'dog', 'name': name, 'role': role}:
            pet = Dog(name, role)
        case {'type': 'cat', 'name': name, 'skill': skill}:
            pet = Cat(name, skill)
        case _:
            pet = None
    return pet

dog = {'type': 'dog', 'name': 'pochi', 'role': 'sentinel'}
cat = {'type': 'cat', 'name': 'mike', 'skill': 'lovely'}

pochi = getPet(dog)
mike = getPet(cat)

print(pochi.role)  # sentinel
print(mike.skill)  # lovely

マッピングパターンの使用例

 この例では、辞書として格納されているペットのデータをgetPet関数内のmatch文で犬か猫かで処理を分岐させ、DogクラスかCatクラスのインスタンスが得られるようにしている。

 マッピングパターンでは、キーと値の双方がサブパターンとなる(ただし、キーについてはリテラルパターンか値パターンのいずれかしか記述できない)。また、シーケンスパターンのアスタリスク付きのサブパターンと同様、ダブルアスタリスク「**」付きのサブパターンも記述できる。上のgetPet関数をこのサブパターンを使って記述したものを以下に示す。

def getPet(arg):
    match arg:
        case {'type': 'dog', **args}:
            pet = Dog(**args)
        case {'type': 'cat', **args}:
            pet = Cat(**args)
        case _:
            pet = None
    return pet

pochi = getPet(dog)
mike = getPet(cat)

print(pochi.role)
print(mike.skill)

ダブルアスタリスク付きのサブパターンの使用例

 シーケンスパターンではアスタリスク付きのサブパターンはシーケンス中の任意の位置に置けたが、マッピングパターンのダブルアスタリスク付きのサブパターンは最後の要素とする必要がある。

クラスパターン

 クラスパターンはsubject valueが何かのクラスのインスタンスかどうかや、インスタンスの属性が特定の値かどうかを調べるのに使用する。以下に例を示す。

match pochi:
    case Cat():
        print(f'{pochi.name} is a cat')
    case Dog():
        print(f"{pochi.name}'s role is a {pochi.role}")

クラスパターンの例

 この例では、上の例で定義した変数pochiがCatクラスのインスタンスかDogクラスのインスタンスかを調べている。

 このインスタンスの属性の値を調べるのであれば、次のように記述する。

match pochi:
    case Dog(name='mike', skill='lovely'):
        print('pochi is in fact a cat')
    case Dog(name='pochi', role='sentinel'):
        print('pochi is pochi')

属性の値を調べる

 属性の値は「キーワード=パターン」のように記述する(これをキーワードパターンと呼ぶ)。そして、「キーワード=パターン」として指定された全ての属性について、パターンと実際の属性の値がマッチすればそのパターンのマッチングは成功する。

 ただし、全ての属性をこのような形式で記述するのはなかなか面倒だ。そこでクラス定義にクラス属性として「__match_args__」を定義できる。

class Dog:
    __match_args__ = ('name', 'role')
    def __init__(self, name, role):
        self.name = name
        self.role = role

__match_args__に位置パターンで使用する属性名を列挙

 __match_args__クラス属性には以下に示す「位置パターン」で(「クラス名(属性値0, 属性値1, ……)」のように位置引数と同様な形式で)マッチするかどうかを調べたい属性を並べたときに、それがどの属性に対応するのかが分かるように、属性名(文字列)を要素とするタプルを記述する。タプルの先頭要素が、位置パターンの先頭要素の属性名として、タプルの次の要素が位置パターンの次要素の属性名として……のように使われる。

 この機構を利用すると上記のコードは以下のように記述できる。

pochi = Dog('pochi', 'sentinel')

match pochi:
    case Dog('mike', 'lovely'):
        print('pochi is in fact a cat')
    case Dog('pochi', 'sentinel'):
        print('pochi is pochi')

__match_args__クラス属性でインスタンスの属性を調べるパターンの記述を簡略化

ORパターン

 ORパターンは、名前から分かるように、複数のパターンのマッチ結果の論理和が全体としてのマッチ結果となるパターンだ。つまり、複数のパターンのいずれかがマッチすれば、全体としてはマッチに成功したと考えられる。

num = 121
match num:
    case 0 | 1 | 120 | 121:
        print('foo')
    case _:
        print('bar')

ORパターン

 これはnumの値が0、1、120、121のいずれかであればマッチするということだ(4つのサブパターンで構成され、それらは全てリテラルパターンとなっている)。

 注意したいのは、ORパターンの中に(例えば、定数との比較のつもりで)キャプチャーパターンを記述してしまうと、それが常に成功してしまうので、そのORパターンは常に成功してしまうようになる点だ。

ASパターン

 ASパターンは、これまでに見てきたパターンに「as 変数」と記述するパターンのこと。ASパターンを付加したパターンが成功すると、指定された変数にその値が代入される。例えば、サブパターンにORパターンを記述したときに、どの値がマッチしたかを記録する目的で使用できる。

order = {'drink': 'BEER', 'food': 'TON-KATSU'}

match order:
    case {'drink': ('BEER' | 'SAKE' | 'COKE') as drink,
          'food': ('TON-KATSU' | 'SASHIMI' | 'TO-FU') as food}:
        print(f'order: {drink} and {food}')

ASパターンの使用例

 この例ではマッピングパターンのサブパターンとして、注文された飲み物とフードを変数drinkとfoodにASパターンを使用して取り出すようにしている。


 今回はPython 3.10で追加された構造的パターンマッチ(match〜case文)について駆け足で説明をした。次回はその他の機能の幾つかを紹介する予定だ。

「Python最新情報キャッチアップ」のインデックス

Python最新情報キャッチアップ

Copyright© Digital Advantage Corp. All Rights Reserved.

前のページへ |       
[an error occurred while processing this directive]
ページトップに戻る