Python での構造パターン マッチング

Mehvish Ashiq 2023年10月10日
  1. 構造パターン マッチングの概要とその重要性
  2. Python で構造パターン マッチングを使用する
Python での構造パターン マッチング

Python 3.10 より前は、他のプログラミング言語で switch-case と呼ばれる構造パターン マッチングを使用する組み込みの方法がありませんでした。 Python 3.10 リリースの時点で、match ... case ステートメントを使用して switch ... case ステートメントをエミュレートすることはできません。

このチュートリアルでは、構造パターン マッチングと Python におけるその重要性を紹介します。 また、match ... case ステートメントの使用方法を示すために、さまざまなパターンを使用しています。

構造パターン マッチングの概要とその重要性

2021 年の初めの時点で、3.9 以下のリリースされた Python バージョンでは match キーワードを使用できませんでした。 当時、私たちは、辞書やネストされた if/elif/else ステートメントを使用して switch ... case をシミュレートすることに慣れていました。

しかし、Python 3.10 では、構造パターン マッチング (match ... case ステートメント) と呼ばれる新しい機能が導入されました。 これは、Java、C++、および他の多くのプログラミング言語にあるような switch ... case ステートメントと同等です。

この新機能により、シンプルで読みやすく、最小限のエラー フロー制御ステートメントを記述できるようになりました。

Python で構造パターン マッチングを使用する

構造パターン マッチングは switch ... case ステートメントとして使用され、これよりも強力です。 どうやって? 以下のいくつかの例を調べて、さまざまな状況での使用法を学びましょう。

match ... case ステートメントの基本的な使い方

コード例:

# >= Python 3.10
colour = "blue"
match colour:
    case "green":
        print("The specified colour is green")
    case "white":
        print("Wow, you've picked white")
    case "green":
        print("Great, you are going with green colour")
    case "blue":
        print("Blue like sky...")

出力:

Blue like sky...

ここでは、最初に blue を含む変数 colour があります。 次に、match キーワードを使用します。これは、colour 変数の値をさまざまな指定されたケースに一致させます。各ケースは、case キーワードで始まり、その後に比較またはチェックするパターンが続きます。

パターンは次のいずれかです。

  • リテラルパターン
  • 攻略パターン
  • ワイルドカード パターン
  • 定値パターン
  • シーケンスパターン
  • マッピング パターン
  • クラスパターン
  • OR パターン
  • セイウチ柄

match ... case ステートメントは、一致した最初の case でのみコードを実行します。

case が一致しない場合はどうなりますか? ユーザーはそれについてどのように知ることができますか? そのために、次のようにデフォルトの case を設定できます。

コード例:

# >= Python 3.10
colour = "yellow"
match colour:
    case "green":
        print("The specified colour is green")
    case "white":
        print("Wow, you've picked white")
    case "green":
        print("Great, you are going with green colour")
    case "blue":
        print("Blue like sky...")
    case other:
        print("No match found!")

出力:

No match found!

match ... case を使用してデータ構造を検出および分解する

コード例:

# >= Python 3.10
student = {"name": {"first": "Mehvish", "last": "Ashiq"}, "section": "B"}

match student:
    case {"name": {"first": firstname}}:
        print(firstname)

出力:

Mehvish

上記の例では、次の 2 行のコードで構造パターン マッチングが実行されています。

# >= Python 3.10
match student:
    case {"name": {"first": firstname}}:

match ... case ステートメントを使用して、student データ構造から抽出することにより、学生の名前を見つけます。 ここで、student は生徒の情報を含む辞書です。

case 行は、student に一致するパターンを指定します。 上記の例を考えると、値が新しい辞書である the "name" キーを持つ辞書を探します。

このネストされた辞書には、値が firstname 変数にバインドされた "first" キーが含まれています。 最後に、firstname 変数を使用して値を出力します。

もっと深く観察すれば、ここでマッピングパターンを学びました。 どうやって? マッピング パターンは {"student": s, "emails": [*es]} のようになり、少なくとも指定されたキーのセットとマッピングに一致します。

すべてのサブパターンが対応する値と一致する場合、キーに対応する値との照合中にサブパターン バインドがバインドされます。 追加のアイテムをキャプチャできるようにしたい場合は、パターンの最後に **rest を追加できます。

キャプチャ パターンとシーケンス パターンで match ... case を使用する

コード例:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [first, *rest]:
            return first + sum_list_of_numbers(rest)


sum_list_of_numbers([1, 2, 3, 4])

出力:

10

ここでは、再帰関数を使用してキャプチャ パターンを使用し、指定されたパターンに一致するものをキャプチャして名前にバインドします。

このコード例では、最初の case は、空のリストと一致する場合、合計として 0 を返します。 2 番目の case は、複数のアイテム/要素のいずれかとリストを照合するために、2つのキャプチャ パターンを含むシーケンス パターンを使用します。

ここでは、リストの最初のアイテムがキャプチャされ、first という名前にバインドされます。2 番目のキャプチャ パターン *rest は、アンパック構文を使用して任意の数のアイテム/要素に一致します。

rest は、最初のものを除いて、数値のすべての項目/要素を持つリストにバインドされることに注意してください。 出力を取得するには、上記のように数値のリストを渡して sum_list_of_numbers() 関数を呼び出します。

ワイルドカード パターンで match ... case を使用する

コード例:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [first, *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            incorrect_type = numbers.__class__.__name__
            raise ValueError(
                f"Incorrect Values. We Can only Add lists of numbers,not {incorrect_type!r}"
            )


sum_list_of_numbers({"1": "2", "3": "4"})

出力:

ValueError: Incorrect Values. We Can only Add lists of numbers, not 'dict'

match ... case ステートメントの基本的な使い方を学びながら、ワイルドカード パターンを使用する概念を学びましたが、そこではワイルドカード パターンの用語を紹介しませんでした。 最初の 2つのケースが一致せず、最後の ケース としてキャッチオール パターンが必要なシナリオを想像してみてください。

たとえば、リストの代わりに他のタイプのデータ構造を取得した場合にエラーを発生させたいと考えています。 ここでは、ワイルドカード パターンとして _ を使用できます。これは、名前にバインドせずに何にでも一致します。 この最後の case にエラー処理を追加して、ユーザーに通知します。

あなたは何を言っていますか? 私たちのパターンはうまくいくでしょうか? 次のように文字列値のリストを渡して sum_list_of_numbers() 関数を呼び出してテストしてみましょう。

sum_list_of_numbers(["1", "2", "3", "4"])

次のエラーが発生します。

TypeError: can only concatenate str (not "int") to str

したがって、パターンはまだ十分に確実ではないと言えます。 なぜ? リスト型のデータ構造を sum_list_of_numbers() 関数に渡しますが、期待した int 型ではなく文字列型の値を持っているためです。

解決方法については、次のセクションを参照してください。

クラス パターンで match ... case を使用する

コード例:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [int(first), *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            raise ValueError(f"Incorrect values! We can only add lists of numbers")


sum_list_of_numbers(["1", "2", "3", "4"])

出力:

ValueError: Incorrect values! We can only add lists of numbers

基本ケース (最初の case) は 0 を返します。 したがって、合計は、数値で加算できる型に対してのみ機能します。 Python は、テキスト文字列と数字を追加する方法を知らないことに注意してください。

そのため、クラス パターンを使用して、パターンを整数のみに一致するように制限できます。 クラス パターンはマッピング パターンに似ていますが、キーではなく属性に一致します。

OR パターンで match ... case を使用する

コード例:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [int(first) | float(first), *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            raise ValueError(f"Incorrect values! We can only add lists of numbers")

sum_list_of_numbers() 関数を、int 型または float 型の値に関係なく、値のリストに対して機能させたいとします。 パイプ記号 (|) で表される OR パターンを使用します。

上記のコードは、指定されたリストに int または float 型の値以外の値が含まれている場合、ValueError を発生させる必要があります。 以下の 3つのシナリオすべてを考慮してテストしてみましょう。

テスト 1: int 型の値を持つリストを渡します。

sum_list_of_numbers([1, 2, 3, 4])  # output is 10

テスト 2: float 型の値を持つリストを渡す:

sum_list_of_numbers([1.0, 2.0, 3.0, 4.0])  # output is 10.0

テスト 3: int 型と float 型を除く他の型を持つリストを渡します。

sum_list_of_numbers(["1", "2", "3", "4"])
# output is ValueError: Incorrect values! We can only add lists of numbers

ご覧のとおり、sum_list_of_numbers() 関数は、OR パターンを使用しているため、int 型と float 型の両方の値に対して機能します。

match ... case をリテラル パターンで使用する

コード例:

# >= Python 3.10
def say_hello(name):
    match name:
        case "Mehvish":
            print(f"Hi, {name}!")
        case _:
            print("Howdy, stranger!")


say_hello("Mehvish")

出力:

Hi, Mehvish!

この例では、match ... case ステートメントの基本的な使用法を学習するときに既に行ったように、明示的な数値や文字列など、リテラル オブジェクトに一致するリテラル パターンを使用します。

これはパターンの最も基本的なタイプであり、Java、C++、およびその他のプログラミング言語に似た switch ... case ステートメントをシミュレートできます。 このページにアクセスして、すべてのパターンについて学習できます。

著者: Mehvish Ashiq
Mehvish Ashiq avatar Mehvish Ashiq avatar

Mehvish Ashiq is a former Java Programmer and a Data Science enthusiast who leverages her expertise to help others to learn and grow by creating interesting, useful, and reader-friendly content in Computer Programming, Data Science, and Technology.

LinkedIn GitHub Facebook