We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Stdlib Mastery step 33 of 55
total_ordering: SemVer, where pre-release sorts before release
Build a comparable SemVer whose ordering is deliberately not
field-lexicographic, so @dataclass(order=True) cannot do it for you.
@total_ordering
class SemVer:
def __init__(self, major: int, minor: int, patch: int, pre: str = "") -> None: ...
@classmethod
def parse(cls, text: str) -> "SemVer": ...
def __str__(self) -> str: ...
-
parse("1.2.3")givespre == "";parse("1.2.3-rc.1")givespre == "rc.1". -
__str__round-trips:"1.2.3"or"1.2.3-rc.1". -
Ordering is by
(major, minor, patch), and then a pre-release sorts before its release:1.2.3-alpha < 1.2.3. Among pre-releases of the same version, compare the tag as a plain string. -
SemVermust be hashable, and equal versions must hash equally.
def solve(versions: list[str], pairs: list[list[str]]) -> dict[str, object]:
Returns:
{"sorted": [str(v) for v in sorted(parsed)],
"comparisons": [[lt, le, gt, ge, eq, ne] for each pair],
"eq_with_str": SemVer(1, 0, 0) == "1.0.0", # False
"lt_with_str": "TypeError" if SemVer(1, 0, 0) < "1.0.0" raises else "compared",
"distinct": len(set(parsed))}
Why not @dataclass(order=True). It compares the fields as a tuple in
declaration order, so (1, 2, 3, "") sorts after (1, 2, 3, "alpha")
because the empty string is lexicographically smallest. That is backwards, and
no arrangement of fields fixes it — the ordering needs a computed key. That is
exactly the case total_ordering exists for. (Where the ordering is
field-lexicographic, @dataclass(order=True) is the better tool: no wrapper
overhead, and field(compare=False) to exclude a field.)
Return NotImplemented, not False. NotImplemented tells the
interpreter to try the reflected operation on the other operand. Returning
False unilaterally declares inequality, and if some other version class
knows how to compare itself with yours you end up with a == b being False
while b == a is True — an asymmetric == that breaks in, dict
lookups, assertEqual and every set operation, depending on which side of the
operator the value landed on. Done right, SemVer(1,0,0) == "1.0.0" is
False and SemVer(1,0,0) < "1.0.0" raises TypeError: equality between
unrelated types is a meaningful “no”, ordering between them is a bug.
Typing. __eq__ must take other: object — it is inherited from object
with that signature and narrowing it violates Liskov, which --strict
reports. Returning NotImplemented from a -> bool method is fine: typeshed
types it as a subclass of Any precisely so the standard dunder idiom
type-checks. And defining __eq__ sets __hash__ to None, so you must
define __hash__ yourself.
Two costs total_ordering is documented to have: the derived methods are
slower than hand-written ones, and they produce more complex stack traces.
Neither matters here; both matter when you sort a million objects.
Good news on the tooling: mypy 2.3 understands the decorator, so “it confuses type checkers” is stale advice.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.