Matchify
Matchify automatically converts eligible if/elif/else chains into
Python 3.10+ match statements while preserving runtime behavior and source
formatting.
Note
Matchify was built with assistance from AI tools. Its implementation and generated transformations may contain mistakes, so review changes and run your project's tests before relying on them. Feedback and bug reports are very welcome.
Examples
Simple equality chain:
# Before
if x == 1:
print("one")
elif x == 2:
print("two")
else:
print("other")
# After
match x:
case 1:
print("one")
case 2:
print("two")
case _:
print("other")
isinstance with attributes:
# Before
if isinstance(node, Point) and node.x == 5:
print("x is 5")
elif isinstance(node, Point):
print("other point")
# After
match node:
case Point(x=5):
print("x is 5")
case Point():
print("other point")
Sequence patterns:
# Before
if len(point) == 2 and point[0] == 0 and point[1] == 1:
print("origin offset")
elif len(point) == 2 and point[0] == 1:
print("other pair")
# After
match point:
case 0, 1:
print("origin offset")
case 1, _:
print("other pair")
Nested patterns (isinstance inside sequences):
# Before
if len(x) == 2 and isinstance(x[0], Point) and x[1] == 2:
print("point and 2")
elif len(x) == 2 and x[0] == 1 and x[1] == 1:
print("ones")
# After
match x:
case Point(), 2:
print("point and 2")
case 1, 1:
print("ones")
Nested sequences:
# Before
if (
len(data) == 2
and len(data[0]) == 2
and data[0][0] == 1
and data[0][1] == 2
and data[1] == 3
):
print("nested list")
elif (
len(data) == 2
and isinstance(data[0], Point)
and len(data[1]) == 2
and data[1][0] == 0
and data[1][1] == 0
):
print("point with coordinates")
# After
match data:
case [1, 2], 3:
print("nested list")
case Point(), [0, 0]:
print("point with coordinates")
Class patterns with sequence attributes:
# Before
class Data:
def __init__(self, value):
self.value = value
obj = Data([1, 2, 3])
if (
isinstance(obj, Data)
and len(obj.value) == 3
and obj.value[0] == 1
and obj.value[1] == 2
and obj.value[2] == 3
):
print("data with list")
elif isinstance(obj, Data):
print("other data")
# After
class Data:
def __init__(self, value):
self.value = value
obj = Data([1, 2, 3])
match obj:
case Data(value=[1, 2, 3]):
print("data with list")
case Data():
print("other data")
Installation
Install "matchify" as a command-line tool with uv:
uv tool install matchify
Or run it without installing:
uvx matchify path/to/project/
Key Features
- Automatic conversion of if/elif/else chains to Python 3.10+ match statements
- Preserves formatting and code structure using LibCST
- Supports multiple pattern types:
- Literal comparisons (
x == 1,x == "value") - Identity checks (
x is None,x is True) - isinstance checks (
isinstance(x, MyClass)) - Class patterns with attributes (
isinstance(p, Point) and p.x == 5) - Sequence patterns (
len(x) == 2 and x[0] == 0 and x[1] == 1) - Nested sequences (
[[1, 2], 3]) - Sequence attributes in class patterns (
Data(value=[1, 2, 3])) - Or patterns for isinstance tuples (
isinstance(x, (int, float))) - Parallel processing for fast conversion of large codebases
- Safe transformations - only converts when semantics are preserved
Usage
# Convert a single file
matchify --write path/to/file.py
# Convert all Python files in a directory
matchify --write path/to/project/
# Convert with verbose output
matchify --write path/to/project/ -v
# Review diffs without writing changes
matchify path/to/project/ --check
# Show eligible conversions as diffs while converting
matchify path/to/project/ --show --write
# Also preview conversions that need a missing --assume value
matchify path/to/project/ --show-all --check
# Use parallel processing (default: number of CPUs)
matchify path/to/project/ --write -j 8
# Enable one risky assumption explicitly
matchify path/to/project/ --write --assume pure-subjects
# Disable all risky assumptions
matchify path/to/project/ --write --safe
# Enable all risky assumptions
matchify path/to/project/ --write --risky
# Convert only chains that meet a stylistic threshold
matchify path/to/project/ --convert-if "isinstance_checks >= 2 or branches >= 4"
--write writes conversions and --check only reports them as diffs; the two
options cannot be combined. --check implies --show and exits with 1 if any
file would change. Explicit --show or --show-all without --write is a
preview-only dry run that still exits 0 when conversions are available. In an
interactive terminal, omitting --write, --check, and the show flags shows a
diff and asks for confirmation before writing. In a non-interactive shell,
choose --write, --check, or --show explicitly.
pre-commit
Matchify provides two pre-commit hooks.
Use matchify to automatically rewrite files, similar to the default Black
hook:
repos:
- repo: https://github.com/15r10nk/matchify
rev: v0.1.0
hooks:
- id: matchify
Use matchify-check to only report files that would be converted without
modifying them:
repos:
- repo: https://github.com/15r10nk/matchify
rev: v0.1.0
hooks:
- id: matchify-check
Risky assumptions
By default, Matchify enables no risky assumptions. --safe makes that explicit,
while --risky enables all available assumptions. See the
risky assumptions documentation
for the semantic tradeoffs and individual --assume names.
--check and --show review the currently eligible conversions as diffs.
--show --write prints the same diffs while converting. Both report how many
conversions were not shown because they need a missing --assume value and
point you to --show-all to preview them. --show-all additionally prints the
required --assume value and a separate diff for each group of conversions
unlocked by that assumption. For if/elif previews, both commands also print
the computed --convert-if metric variables for that conversion.
Selective conversion with --convert-if
By default, Matchify converts every eligible chain. This is equivalent to:
--convert-if True
Use --convert-if to choose a different threshold based on source and generated
metrics:
matchify path/to/project/ --convert-if "branches >= 4 and guard_conditions == 0"
Use --convert-if True if you want that default spelled out explicitly in
scripts or examples. See the
selective conversion documentation
for the expression syntax and complete metric reference.
Development
Development and repository-testing notes are in CONTRIBUTING.md.
Issues
If you encounter any problems, please report an issue along with a detailed description.
License
Distributed under the terms of the MIT license, "matchify" is free and open source software.