Batu Lab NotesPractical developer guides

Sort version-like tuples with cmp_to_key

By Batu ยท English technical notes

Also published in our Blogger archive.

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