Sort version-like tuples with cmp_to_key

functools.cmp_to_key adapts a two-argument comparator for APIs such as sorted() that normally expect a one-argument key function. Here, compare_versions() receives two dotted strings and returns a negative, zero, or positive integer. It converts "1.2" to (1, 2), then pads only the shorter side with zeroes before comparing. Consequently, "1.2" and "1.2.0" compare equal; Python's stable sort preserves their original relative order.

version_parts() deliberately accepts only components that int() can convert and rejects negative values. The resulting output orders 1.2.9 before 1.10 numerically, unlike a plain lexical string sort. The assertions check both the complete ordering and the trailing-zero rule.

This is a version-like convention, not a full packaging-version implementation. It does not model prerelease labels such as 1.0rc1, epochs, build metadata, or an arbitrary number of semantic-version rules. For a format with those requirements, define its comparison policy explicitly or use the relevant domain library. cmp_to_key is useful when that policy genuinely needs pairwise comparison; a tuple key is often simpler when it does not.

AI assistance was used to draft this Batu Lab Notes article.

from functools import cmp_to_key


def version_parts(text):
    parts = tuple(int(piece) for piece in text.split("."))
    if not parts or any(piece < 0 for piece in parts):
        raise ValueError("version parts must be non-negative integers")
    return parts


def compare_versions(left, right):
    left_parts, right_parts = version_parts(left), version_parts(right)
    width = max(len(left_parts), len(right_parts))
    left_key = left_parts + (0,) * (width - len(left_parts))
    right_key = right_parts + (0,) * (width - len(right_parts))
    return (left_key > right_key) - (left_key < right_key)


versions = ["1.10", "1.2.9", "1.2", "2", "1.2.0"]
ordered = sorted(versions, key=cmp_to_key(compare_versions))
assert ordered == ["1.2", "1.2.0", "1.2.9", "1.10", "2"]
assert compare_versions("3.0", "3") == 0
print(", ".join(ordered))

Expected stdout:

1.2, 1.2.0, 1.2.9, 1.10, 2

Sources

Comments

Popular posts from this blog

Compare Two Text Files in Python Without Modifying Them

Seven CSV Quality Checks to Run Before Importing Data

Explain why SQLite transactions cannot make an HTTP call atomic