Sort version-like tuples with cmp_to_key
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