Composable patterns and regular expressions for Python objects.
Python Pattern Matching is a small, pure-Python library for matching values, destructuring records and collections, binding names, applying predicates, and expressing regular-expression-style patterns over sequences of arbitrary Python objects.
Patterns are ordinary runtime values. There are no import hooks, AST transforms, or special syntax.
pip install patternmatchingPython 3.10 through 3.14 are supported. The package includes type information.
match() returns an immutable Match containing bindings, or None when the
pattern does not match:
from patternmatching import bind, match
result = match(
{"event": "created", "user": {"name": "Ada", "id": 42}},
{"event": "created", "user": {"name": bind.name}},
)
assert result is not None
assert result.name == "Ada"
assert result["name"] == "Ada"A Match is always truthy, even when it has no bindings, so existing
if match(value, pattern): code remains natural. Failed matches return None.
The latest successful results are also retained in bound for the original
ambient-binding style:
from patternmatching import bound
assert bound.name == "Ada"Literal patterns compare equal to values. A class pattern uses isinstance;
when the value is itself a class, issubclass is used. Lists and tuples match
recursively and require the same concrete sequence type and length.
assert match(42, int)
assert match(bool, int)
assert match([1, "two", [3.0]], [int, str, [float]])Any attribute of bind creates a named binding. Reusing a name turns it into
an equality constraint. bind.any matches one value without storing it.
assert match(("left", "left"), (bind.side, bind.side))
assert bound.side == "left"
assert match([1, 2, 3], [bind.any, bind.middle, bind.any])
assert bound.middle == 2
assert match(("left", "right"), (bind.side, bind.side)) is NoneBindings with names that collide with mapping methods, such as items, remain
available through result["items"].
A bare mapping is a recursive subset pattern. Every pattern key must exist and its value must match; extra keys in the input are allowed.
payload = {
"user": {"name": "Ada", "roles": ["admin", "author"]},
"request_id": 42,
}
result = match(
payload,
{"user": {"name": bind.name, "roles": [str, bind.role]}},
)
assert result == {"name": "Ada", "role": "author"}Use equal(value) when ordinary equality is intended instead of structural
matching:
from patternmatching import equal
assert match(payload, equal(payload))
assert match(payload, equal({"request_id": 42})) is Nonerecord() matches an instance and selected attributes. Positional patterns use
the class's __match_args__; keyword patterns use getattr. Dataclasses define
__match_args__ automatically, and regular classes can define it themselves.
from dataclasses import dataclass
from patternmatching import record
@dataclass
class Point:
x: int
y: int
result = match(Point(2, 3), record(Point, bind.x, y=int))
assert result == {"x": 2}Keyword-only matching works for arbitrary attribute objects, including classes
without __match_args__:
class User:
def __init__(self, name, active):
self.name = name
self.active = active
some_user = User("Ada", True)
assert match(some_user, record(User, name=bind.name, active=True))Record and mapping bindings are transactional: a failed nested pattern does not leak partial bindings into another alternative or result.
like(pattern, name="match") applies a callable to the value. A falsy result,
or a common attribute/lookup/type/value error, is a mismatch. A truthy result is
bound under name; pass name=None when no result is needed.
Strings, bytes, and compiled re.Pattern values use re.match semantics:
from patternmatching import like
result = match("item-42", like(r"item-(\d+)"))
assert result.match.group(1) == "42"
assert match(b"item-42", like(rb"item-(\d+)"))The sequence helpers compose regex-like patterns with repetition, alternatives, exclusion, capturing groups, greediness, and backtracking. They work on strings, bytes, lists, tuples, and other indexable sequences.
from patternmatching import group, padding, repeat
events = ["noise", "BEGIN", 1, 2, 3, "END", "tail"]
pattern = (
padding
+ ["BEGIN"]
+ (int * repeat(min=1)) * group("values")
+ ["END"]
)
result = match(events, pattern)
assert result.values == [1, 2, 3]match() starts at offset zero and may consume only a prefix, like re.match.
fullmatch() requires the entire generic sequence to be consumed:
from patternmatching import fullmatch
pattern = "ab" * group("prefix")
assert match("abc", pattern) == {"prefix": "ab"}
assert fullmatch("abc", pattern) is None
assert fullmatch("ab", pattern) == {"prefix": "ab"}anyonematches one object.anythingmatches zero or more objects greedily.somethingmatches one or more objects greedily.paddingmatches zero or more objects non-greedily.pattern * repeat(min=0, max=inf, greedy=True)repeats a pattern.pattern * maybematches zero or one occurrence.either(a, b, ...)matches the first successful alternative.exclude(a, b, ...)consumes one object if none of its alternatives match.pattern * group(name)captures the matching slice.left + rightconcatenates sequence patterns.
Multiplication binds more tightly than concatenation, so parentheses often make intent clearer:
pattern = ["("] + (int * repeat(min=1)) * group("items") + [")"]When a regex-like helper pattern is applied to str or bytes, the matcher
conservatively translates the complete supported pattern to Python's re
engine. Translation is cached. Supported patterns include literals, anyone,
repetition, non-binding alternatives and exclusions, captures, and simple
bindings/backreferences.
The optimization is all-or-nothing. If any part cannot be represented with the
same semantics—for example an arbitrary predicate—the library runs the generic
engine for the whole pattern. There is no mixed execution and no semantic
approximation. Custom matchers may disable translation with
Matcher(native=False).
Every successful call returns a Match and pushes that same object onto the
matcher's bound history. The default history keeps the ten latest successes.
from patternmatching import Matcher
matcher = Matcher(bound_limit=3)
result = matcher.match([1, 2], [bind.left, bind.right])
assert result == {"left": 1, "right": 2}
assert matcher.bound.right == 2
assert len(matcher.bound) == 1bound.pop() removes the latest result and bound.reset() clears the current
context. bound.scope() restores the prior history on exit and works as both a
context manager and a sync or async function decorator:
with bound.scope():
assert match(42, bind.answer)
assert bound.answer == 42
@bound.scope()
def parse(value):
return match(value, bind.value)Binding state is isolated with contextvars, so threads and asynchronous tasks
do not overwrite one another. Set bound_limit=None for unbounded history.
In v4, bound.reset() is deliberately only a clearing operation. Replace the
old @bound.reset decorator spelling with @bound.scope().
Pattern objects can implement __match__(matcher, value). Raise
patternmatching.Mismatch to reject the value or return normally to accept it:
import patternmatching
class Between:
def __init__(self, low, high):
self.low = low
self.high = high
def __match__(self, matcher, value):
if not self.low <= value <= self.high:
raise patternmatching.Mismatch
assert match(7, Between(1, 10))Matcher also accepts an ordered collection of matching cases for applications
that define an entire matching vocabulary. Native text acceleration is enabled
only for the default case set.
- Successful
match()calls now returnMatchinstead of the matched value or internal action result. Failed calls still produce a falsy result (None). - Use
fullmatch()when a sequence helper must consume the entire input. boundkeeps ten results by default and is context-local.- Replace
@bound.resetwith@bound.scope();bound.reset()now only clears. - Bare mappings are recursive subset patterns. Wrap a mapping in
equal()to request ordinary equality. - Use
record()for dataclasses, named tuples, and attribute-based objects.
Run the supported Python matrix with Nox and uv:
uvx nox -s testsThe tests include doctests, v4 behavior and concurrency coverage, native/generic differential tests, and object-pattern tests adapted from CPython's regular expression tests.
Python Pattern Matching is copyright 2015–2026 Grant Jenks and licensed under the Apache License, Version 2.0.