Skip to main content

Matchify

ci Docs pypi version Python Versions PyPI - Downloads GitHub Sponsors

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)))
  • 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)

Source distribution for matchify 0.3.0
File Size Uploaded
matchify-0.3.0.tar.gz 186.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for matchify 0.3.0
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page