Blog

Python 3.10 führt Pattern Matching ein

Giovanni Lanzani

Aktualisiert Oktober 20, 2025
6 Minuten

Neulich habe ich auf LinkedIn gefragt, ob sich die Leute über das Pattern Matching in Python 3.10 freuen würden.

Ein Drittel der Befragten wusste nicht, was Pattern Matching ist. Ein perfekter Anlass also, einen Blogbeitrag darüber zu schreiben!

Wie bei allen Konstrukten ist der Mustervergleich keine Notwendigkeit für Programmiersprachen. Python erreichte Version 3.10, bevor es sie bekam. Aber es ist eine Funktion, die es uns ermöglicht, Code klarer und oft auch prägnanter zu schreiben.

Und wenn Sie Spaß an funktionalen Programmiersprachen wie Erlang, Haskell usw. haben, haben Sie beim Schreiben von Python wahrscheinlich die Mustererkennung vermisst.

Ich habe es getan und freue mich, dass diese Funktion in Python 3.10 (derzeit in Beta 4) verfügbar ist.

In Python funktioniert der Mustervergleich wie folgt

match statement:
    case condition_1:
        do_something()
    case condition_2:
        do_something_else()
    case _:
        do_something_else_as_nothing_matched()

Python wird prüfen, ob statement passt zu condition_1, dann Zustand2, und wenn nichts übereinstimmt, wird es übereinstimmen (eine Art Sammelbegriff).

Die condition kann ziemlich schlau sein, z.B. sagen wir statement ist (3, 5) und condition_1 ist (a, b). Wir können etwas schreiben wie

match (3, 5):
    case (a, ):
        print(a)  # this does **not** match as (a, ) is a single element tuple
                  # while (3, 5) is a two elements tuple
    case (a, b):
        print(b ** 2)  # this will print 25 as (a, b) is a two element tubple
                       # just like (3, 5)

Sie sehen schon, wie mächtig das sein kann!

Um es in Aktion zu sehen, habe ich einen Code ausgegraben, der einige if/elif/else Anweisungen enthält.

Der ursprüngliche Code ist etwas umfangreicher und enthält zusätzliche Funktionen, die im Moment nicht in Frage kommen, daher habe ich die interessanten Teile isoliert:

from typing import Any, Dict, Iterable, Hashable, Optional

def extract_fields_from_records(
    records: Iterable[Dict], 
    fields: set[Hashable],
    missing: str,
    _default: Dict[Hashable, Hashable]=dict()
    ) -> Iterable[Dict]:
    """
    Returns a generator of dictionaries whose keys are present in fields, 
    starting from an iterable of dictionaries.

    :param records: A iterable of dictionaries
    :param fields: Fields to include
    :param missing: How to handle missing fields. If omit, missing fields are
    simply omitted. If fill, missing fields are added with the default value
     _default. If raise, a KeyError is raised if any values are missing.
    :param _default: When missing="fill" look up by key for the value to 
    fill.
    """

    if missing == "omit":
        _records = (
            {k: v for k, v in rec.items() if k in fields} for rec in records
            )
    elif missing == "fill":
        _records = (
            {k: rec.get(k, _default.get(k, None)) for k in fields} 
            for rec in records
            )
    elif missing == "raise":
        _records = ({k: rec[k] for k in fields} for rec in records)
    else:
        raise ValueError(
            "Unknown value for missing. Valid values are"
            " 'omit', 'fill' and 'raise'."
        )
    return _records

Die Verwendung ist einfach

records = [
    {"age": 25, "height": 1.9},
    {"age": 45, "height": 1.6, "country": "NL"},
]

list(
    extract_fields_from_records(
        records, 
        {"age", "country"},
        missing="fill",
        _default={"country": "World"}
    )
)

was zu


[
    {'country': 'World', 'age': 25},
    {'country': 'NL', 'age': 45}
]

Wie würde diese Funktion also mit einem Mustervergleich aussehen? Es stellt sich heraus, dass es ziemlich einfach ist


def _extract_fields_from_records(
    records: Iterable[Dict], 
    fields: set[Hashable], 
    missing: str,
    _default: Dict[Hashable, Hashable]=dict()
    ) -> Iterable[Dict]:

    match missing:
        case "omit":
            fields = set(fields)
            _records = (
                {k: v for k, v in rec.items() if k in fields} 
                for rec in records
                )
        case "fill":
            _records = (
                {k: rec.get(k, _default.get(k, None)) for k in fields} 
                for rec in records
                )        
        case "raise":
                _records = ({k: rec[k] for k in fields} for rec in records)
        case _:
            raise ValueError(
                "Unknown value for missing. Valid values are"
                " 'omit', 'fill' and 'raise'."
            )

    return _records

Dieses Beispiel ist jedoch einfach, da es sehr "linear" ist und ich mir nur etwas Tipparbeit spare, indem ich missing == jedes Mal vermeide. Es war einfach, es mit if/else auszudrücken.

Der Musterabgleich wird jedoch leistungsfähig, wenn wir Python bitten, das Muster, das wir abgleichen möchten, zu zerlegen.

Nehmen wir an, wir erhalten Daten von irgendwoher und müssen sie in die richtige Form bringen (wie oft passiert das schon, nicht wahr!)

Die eingehenden Daten können die folgende Form haben

{
    "generic_key": [1, 2, 3], 
    # other stuff
}

oder

{
    "generic_key": 1,   # integer and not a list
    # other stuff
}

oder

{
    "log_me": "message",
}

In diesem letzten Fall sollten wir die Daten nicht verarbeiten und nur die message protokollieren.

In den ersten beiden Fällen müssen wir stattdessen etwas mit den Elementen der Liste oder der einzelnen Ganzzahl tun. Und die Ausgabe sollte immer eine Liste sein, denn wir wollen nicht, dass die Benutzer unserer Funktion weitere Logik schreiben müssen, um die Aufteilung zwischen Liste und Ganzzahl zu verarbeiten. Wie kann man eine solche Funktion ohne Mustervergleich schreiben?

from typing import Optional

def transform_dictionary(dct: dict) -> Optional[list[int]]:
    message = dct.get("log_me")
    values = dct.get("generic_key", [])
    if message:
        print(message)  # this should be a log statement!!
    elif isinstance(values, list):
        return [value ** 2 for value in values]  # this is our transformation, insert your own
    elif isinstance(values, int):
        return [values ** 2]
    else:
        ValueError(f"Input %{dct} is not of the required shape")

transform_dictionary({"log_me": "error"})
print(transform_dictionary({"generic_key": [1, 2, 3]}))
print(transform_dictionary({"generic_key": 1}))
> error
> [1, 4, 9]
> [1]

Das oben beschriebene funktioniert gut, und wahrscheinlich schreiben Sie Python heute so, aber bemerken Sie den mentalen Aufwand, der entsteht, wenn Sie versuchen, die Datenstruktur zu verstehen, die Sie erhalten? Ohne meine obige Erklärung ist das schwierig. Der Mustervergleich macht es einfacher zu verstehen!

def _transform_dictionary(dct: dict) -> Optional[list[int]]:
    match dct:
        case {"generic_key": list(values)}:
            return [value ** 2 for value in values]
        case {"generic_key": int(value)}:
            return [value ** 2]
        case {"log_me": message}:
            print(message)
        case _:
            ValueError(f"Input %{dct} is not of the required shape")

_transform_dictionary({"log_me": "error"})
print(_transform_dictionary({"generic_key": [1, 2, 3]}))
print(_transform_dictionary({"generic_key": 1}))
> error
> [1, 4, 9]
> [1]

Wenn ich jetzt den Code lese, verstehe ich die Formate, die dct haben kann, ohne dass eine Dokumentation erforderlich ist. Wir sehen auch einige leistungsstarke Funktionen, die wir vorher nicht kannten. Durch das Schreiben von list(values) und int(value), bindet Python - beziehungsweise - [1, 4, 9] zu values und 1 zu value.

Es gibt natürlich noch viel mehr (z.B. Wachen): Falls Sie neugierig sind, finden Sie in PEP 634 die vollständige Spezifikation und in PEP 636 ein Tutorial.

Das war's, danke fürs Lesen. Folgen Sie mir auf Twitter @gglanzani für mehr gute Nachrichten!

Verfasst von

Giovanni Lanzani

Contact

Let’s discuss how we can support your journey.