Skip to content

← Under the Hood: Objects, Memory, Speed step 11 of 35

Easy Primitives

Identity vs equality: fix the planted `is`

A Version value type with a bug that passes every test written in a REPL and fails on every value that arrived from real data.

Version is a frozen, slotted, ordered dataclass with fields major, minor, patch and label. Version.parse("2.0.0-beta") splits on the first -, so the label comes out of str.partition — a freshly allocated string, not a literal.

Two things to fix.

1. is_prerelease uses is. The starter reads:

return self.label is "alpha" or self.label is "beta" or self.label is "rc"

which is False for every label that came from parsing. The compiler warns about the literal form since 3.8, and the warning does not survive being refactored into a module constant, so learn the shape rather than the warning. PRERELEASE_LABELS is already defined for you.

2. label takes part in equality, and it should not. Two versions that differ only in their pre-release label are the same release for the purpose of this type: they must compare equal, hash equal, and collapse in a set. That is what field(default="", compare=False) is for, and it is a modelling decision rather than a formatting one — you are declaring that the label is metadata, not identity.

def solve(specs: list[str]) -> dict[str, object]: ...

returns:

  • "sorted" — the deduplicated versions, ascending, each formatted "major.minor.patch".
  • "unique" — how many distinct versions there were.
  • "prerelease" — the original spec strings whose label is a known pre-release label.
  • "eq_foreign"versions[0] == object(), which must be False and must not raise. The dataclass-generated __eq__ returns NotImplemented for a foreign type, Python tries the reflected comparison, both decline, and the fallback is identity.
  • "hash_stable" — that the number of distinct hashes equals the number of distinct versions, i.e. that __eq__ and __hash__ agree.

Empty input must return empty results rather than raising.

The production consequence. if status is "active" works in tests because test fixtures are literals and literals in one compilation unit are folded to one object. Production data comes from JSON, from a database driver, from a socket — none of which produce interned strings. The branch silently flips and nothing raises.

Your submission must pass mypy --strict, which includes --strict-equality.

Loading visualization…