Python Match Case Sequence Pattern: Lists and Tuples
python match case sequence pattern: Learn how to use Python's match case sequence pattern to match lists, tuples, and other sequences with clear examples and common pi...
Python's match case statement, introduced in Python 3.10, provides structural pattern matching. The python match case sequence pattern lets you match against lists, tuples, and other sequences by their structure and length. This article explains how to use sequence patterns effectively, including variable-length matching, nesting, and common pitfalls.
What Is a Sequence Pattern in Python Match Case?
A sequence pattern is a pattern that matches an object if it is a sequence with a specific length and element structure. In a match case statement, you write a sequence pattern as a list or tuple of sub-patterns. For example, [x, y] matches any sequence with exactly two elements, binding the first element to x and the second to y. The pattern (a, b, c) works the same way but uses tuple syntax. Both are equivalent in matching behavior; the choice is stylistic.
Sequence patterns are part of Python's structural pattern matching, which allows you to decompose data based on its shape rather than just its value. This is particularly useful when processing heterogeneous data like command-line arguments, parsed tokens, or configuration structures.
Basic Sequence Pattern Syntax
The simplest sequence pattern specifies a fixed number of elements. The following example matches a list with exactly three elements:
def process_command(command): match command: case ["move", x, y]: print(f"Move to ({x}, {y})") case ["attack", target]: print(f"Attack {target}") case _: print("Unknown command")
Here, the first case matches a list of three elements where the first element is the string "move". The second case matches a two-element list starting with "attack". The wildcard _ matches anything, serving as a fallback. The pattern matches any sequence, not just lists. A tuple like ("move", 5, 8) also matches the first case because the pattern checks length and element values, not the container type.
You can also match an empty sequence with [] or (). This is useful for handling empty input explicitly.
Matching Variable-Length Sequences with Star Patterns
When you need to match sequences of varying length, use a star pattern. The star * binds the remaining elements to a list. For example, to match a command that starts with a verb and has any number of arguments:
def parse_command(line): match line.split(): case [verb, *args]: print(f"Verb: {verb}, args: {args}") case []: print("Empty input")
The pattern [verb, *args] matches any non-empty sequence. The first element is bound to verb, and the rest, as a list, is bound to args. You can place the star anywhere, but only one star is allowed per pattern. For instance, case [*prefix, last] matches a sequence of at least one element, binding the last element to last and the preceding ones to prefix.
Star patterns also work with tuple syntax. (*rest, final) is equivalent to [*rest, final]. Using a star pattern is a common way to handle variable-length input without manual slicing.
Nested Sequence Patterns
Sequence patterns can be nested to match complex structures. For example, consider a list of coordinates where each coordinate is a tuple:
def describe_shape(points): match points: case [(x1, y1), (x2, y2)]: print(f"Line from ({x1},{y1}) to ({x2},{y2})") case [(x, y), *rest]: print(f"First point at ({x},{y}), plus {len(rest)} more") case []: print("No points")
The first case matches a list of exactly two tuples, each with two elements. The second case matches a non-empty list where the first element is a two-element tuple. Nested patterns let you validate and extract data in one step, reducing the need for separate type checks.
You can also combine nested patterns with literal values. For instance, case [("start", x), "end"] matches a two-element list where the first element is a tuple with the string "start" and any value, and the second element is the string "end".
Common Pitfalls and Edge Cases
Sequence patterns match any object that supports len() and indexing, which includes lists, tuples, and even strings. This can lead to surprising behavior. For example, the pattern [a, b] matches a two-character string like "hi", binding a to 'h' and b to 'i'. If you intend to match only lists or tuples, you need to guard against strings or use class patterns.
Another pitfall is forgetting that the wildcard _ is a valid pattern that matches anything, but it does not bind a value. If you need to capture an element but ignore it, use a name that starts with an underscore, like _unused. However, a single _ is the only name that does not bind; any other name binds the value.
Order matters in match case. Cases are evaluated top to bottom, and the first matching case is used. Put more specific patterns before more general ones. For example, place case [x, y] before case [x, *rest] to ensure two-element sequences are handled by the specific case.
Empty sequence patterns are useful but easy to overlook. A pattern [] matches only an empty list or tuple, but not None or other objects. Always include a fallback case case _ to handle unexpected input gracefully.
Performance and Maintainability Considerations
Sequence pattern matching has a runtime cost proportional to the length of the sequence and the number of patterns checked. Each case performs a length check and then element-by-element comparisons. This is similar to manual unpacking and conditionals, so there is no significant overhead for typical use cases. For very large sequences, consider whether a full match is necessary or if you can match on a prefix.
From a maintainability perspective, sequence patterns make code more declarative and reduce boilerplate. They are especially valuable when the structure of the data is stable and known at development time. However, overusing patterns for simple checks can reduce readability. Use sequence patterns when they make the control flow clearer than a series of if statements.
One important limitation is that sequence patterns do not support arbitrary predicates directly. If you need to match based on a condition (e.g., a number greater than 10), you must use a guard. For example:
case [x, y] if x > y: print("First is larger")
Guards add a boolean expression that must be true for the case to match. This keeps the pattern concise while still allowing conditional logic.
When to Use a Sequence Pattern Instead of Manual Unpacking
Sequence patterns shine when you have nested or variable-length structures that would otherwise require multiple checks and indexing. A manual approach might look like:
if len(commands) == 3 and commands[0] == "move": x, y = commands[1], commands[2]
The match case version is more readable and avoids repeated indexing. Use sequence patterns when the structure is the primary way to distinguish between cases. If the logic depends mainly on values, a simple if chain might be clearer.
Sequence patterns also integrate well with other pattern types. You can combine them with literal patterns, capture patterns, and wildcards to express complex conditions in a single case. This reduces the number of branches and makes the code easier to reason about.
Finally, remember that match case requires Python 3.10 or later. If your project supports older Python versions, you cannot use this syntax without a backport like match_case from PyPI, but that is rarely worth the complexity. For new code on modern Python, sequence patterns are a robust tool for handling structured data.