We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Data Modelling and Invariants step 24 of 25
order=True, the 3.13 __eq__ change, and orderings that are not tuples
bisect, heapq, sorted‘s stability guarantee, every low <= x <= high range
check and every cache-invalidation rule assume that == and <= tell a
consistent story. When they disagree, none of those tools is wrong — your type
is. And the disagreement is invisible in review.
order=True builds tuples; since 3.13 __eq__ does not
@dataclass(order=True) generates comparisons as
def __lt__(self, other):
if other.__class__ is self.__class__:
return (self.a, self.b) < (other.a, other.b)
return NotImplemented
Before 3.13, __eq__ was generated the same way — a tuple comparison. In 3.13
__eq__ changed to compare fields individually, which is faster and, for one
input, differently wrong. With a NaN field:
-
3.13+:
x == yisFalse(NaN != NaN), whilex <= yandx >= yare bothTrue(because tuple comparison short-circuits onisidentity for the NaN element before falling back to==). -
3.12: both were
True.
So the same class, same data, gives a different answer to x == y depending on
the interpreter. If you carry floats in an ordered dataclass, that is a
“works on my machine” waiting to happen.
Three more properties of order=True worth internalising:
-
It is not polymorphic.
other.__class__ is self.__class__means comparing aVersionto aTaggedVersionraisesTypeError, even though one is a subclass of the other. -
Heterogeneous types blow up lazily. A
(int, str)pair only reaches thestrcomparison when theints tie, so theTypeErrorappears on the day your data happens to contain a duplicate. - It compares every comparing field, in declaration order. That is almost never the sort key you meant. It is a lexicographic tuple order, and real domain orderings — versions, priorities, intervals, money with currencies — usually are not.
SemVer is the canonical example of “not tuple order”
Per semver.org §11:
-
Compare
major,minor,patchnumerically. -
A version with a prerelease has lower precedence than the same version
without one.
1.0.0-alpha < 1.0.0. Plain tuple order gets this exactly backwards, because("alpha",) > (). -
Compare prerelease identifiers left to right. Identifiers made only of digits
compare numerically (so
beta.2 < beta.11, which string order reverses); identifiers with letters compare in ASCII order; a numeric identifier always has lower precedence than an alphanumeric one; and if all shared identifiers are equal, the version with more identifiers wins. -
Build metadata is ignored entirely.
1.0.0+001and1.0.0+002have equal precedence.
Your task
Complete the frozen Version:
-
Version.parse(text)fillsmajor,minor,patch,prerelease(a tuple of identifier strings, empty when absent) andbuild, and raisesValueError(f"not a semantic version: {text!r}")on a non-match; -
buildmust be excluded from equality — onefield()argument, no custom__eq__; -
implement
__lt__by hand, following the four rules above. ReturnNotImplementedwhenotheris not aVersion, so Python falls back rather than raising from inside your method.
Then solve(versions) returns:
-
"sorted"— the input strings, ordered by precedence. Usesorted(versions, key=Version.parse);sortedonly ever calls<, and it is stable, so versions of equal precedence keep their input order. -
"eq_build_ignored"—Version.parse("1.0.0+a") == Version.parse("1.0.0+b"). -
"eq_other_type"— comparing aVersionto a plainstr. The generated__eq__returnsNotImplemented, so Python falls back to identity and this isFalse— it must not raise. (The helper routes throughoperator.eqbecause--strict-equality, which--strictturns on, rejects the literalVersion(...) == "1.0.0"as acomparison-overlaperror. That is mypy being right: the comparison is alwaysFalse, which is usually a bug.) -
"lt_other_type"—"TypeError", because ordering has no fallback: when both sides returnNotImplemented, Python raises.
Types. order=True, eq=False is rejected statically as well as at runtime.
And mypy accepts return NotImplemented from a comparison dunder annotated
-> bool — it special-cases exactly this idiom, so you do not need a
# type: ignore.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.