match statement (structural pattern matching)

Structural pattern matching

The big Python 3.10 feature everyone is talking about is structural pattern matching .

This feature is very powerful but probably not very relevant for most Python users .

One important note about this feature: match and case are still allowable variable names so all your existing code should keep working (they’re soft keywords).

Matching the shape and contents of an iterable

You could look at the new match/case statement as like tuple unpacking with a lot more than just length-checking.

You could look at the new match/case statement as like tuple unpacking with a lot more than just length-checking.

Compare this snippet of code from a Django template tag :

args = token.split_contents()
if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
    raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:]))
return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4])

To the same snippet refactored to use structural pattern matching:

match token.split_contents():
    case [name, "for", code "as" info]:
        return GetLanguageInfoNode(parser.compile_filter(code), info)
    case [name, *rest]:
        raise TemplateSyntaxError(f"'{name}' requires 'for string as variable' (got {rest!r})")

Notice that the second approach allows us to describe both the number of variables we’re unpacking our data into and the names to unpack into (just like tuple unpacking) while also matching the second and third values against the strings for and as. If those strings don’t show up in the expected positions, we raise an appropriate exception.

Structural pattern matching is really handy for implementing simple parsers , like Django’s template language. I’m looking forward to seeing Django’s refactored template code in 2025 (after Python 3.9 support ends) .

Complex type checking

Structural pattern matching also excels at type checking .

Strong type checking is usually discouraged in Python, but it does come crop up from time to time.

The most common place I see isinstance checks is in operator overloading dunder methods (__eq__, __lt__, __add__, __sub__, etc). I’ve already upgraded some Python Morsels solutions to compare and contrast match-case and isinstance and I’m finding it more verbose in some cases but also occasionally somewhat clearer.

For example this code snippet (again from Django):

if isinstance(value, str):  # Handle strings first for performance reasons.
    return value
elif isinstance(value, bool):  # Make sure booleans don't get treated as numbers
    return str(value)
elif isinstance(value, (decimal.Decimal, float, int)):
    if use_l10n is False:
        return str(value)
    return number_format(value, use_l10n=use_l10n)
elif isinstance(value, datetime.datetime):
    return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n)
elif isinstance(value, datetime.date):
    return date_format(value, use_l10n=use_l10n)
elif isinstance(value, datetime.time):
    return time_format(value, 'TIME_FORMAT', use_l10n=use_l10n)
return value

Can be replaced by this code snippet instead:

match value:
    case str():  # Handle strings first for performance reasons.
        return value
    case bool():  # Make sure booleans don't get treated as numbers
        return str(value)
    case decimal.Decimal() | float() | int():
        if use_l10n is False:
            return str(value)
        return number_format(value, use_l10n=use_l10n)
    case datetime.datetime():
        return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n)
    case datetime.date():
        return date_format(value, use_l10n=use_l10n)
    case datetime.time():
        return time_format(value, 'TIME_FORMAT', use_l10n=use_l10n)
    case _:
        return value

Note how much shorter each condition is.

That case syntax definitely takes some getting used to, but I do find it a bit easier to read in long isinstance chains like this.

Benchmark