Matchify
Matchify automatically converts eligible if/elif/else chains into
Python 3.10+ match statements while preserving runtime behavior and source
formatting.
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)))
- Literal comparisons (
- 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.
Metadata
Release files for matchify 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| matchify-0.3.0.tar.gz | 186.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| matchify-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 227.2 kB
Release files / matchify-0.3.0.tar.gz
| Download URL | matchify-0.3.0.tar.gz |
|---|---|
| Size | 186.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1555673b6dc6619c908cf1dba9053a75f0c633c8e9367989c0072cd520430e97
|
|
BLAKE2b-256 checksum How to use checksums |
c42021cefd51c614b1140e63e5b1149be7fd08054a5ade1ce3ba20694134136d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Omarchy","version":"4.0.3","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / matchify-0.3.0-py3-none-any.whl
| Download URL | matchify-0.3.0-py3-none-any.whl |
|---|---|
| Size | 40.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
72497cfdfce95bed195d7e76b51332ac9ce144e79c54b42de51551b5befaef71
|
|
BLAKE2b-256 checksum How to use checksums |
c296be2bbee820cca92787a04178714442a59a52404c6e66b5920ff8608a6a75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Omarchy","version":"4.0.3","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|