Why compare sys.version_info as a tuple instead of parsing the sys.version string?
answer
- Ask the interpreter, do not read its banner
- Numbers compare element-wise; text does not
- A five-field named tuple in sys
- 3.9 sorts after 3.14 as text
- sys.version_info >= (3, 11)
basics
~20 ssys.version_info is a five-field named tuple, so comparing it against a tuple such as (3, 11) is an element-wise numeric comparison. sys.version is a free-form display string, and text comparison wrongly orders 3.9 after 3.14.
solid answer
~40 s`sys.version_info` is a named tuple of `(major, minor, micro, releaselevel, serial)` — on CPython 3.14.0 that is `(3, 14, 0, 'final', 0)`. Tuple comparison is element-wise and numeric, so the idiom `if sys.version_info >= (3, 11):` reads exactly as intended and a shorter comparison tuple simply stops early. `sys.version` is a multi-line human-readable banner ("3.14.0 (main, ...) [Clang ...]"), so slicing or `float()`-ing it is fragile: it broke widely when the minor version reached double digits in 3.10, because `"3.9" > "3.14"` lexicographically and `float("3.10")` equals `3.1`. Always compare with `>=` against the version that introduced the feature, never `==`. `sys.hexversion` is the same information packed into one integer if you prefer a single number.
code
python · 5 linesimport sys
print("3.9" < "3.14") # False: lexicographic, '9' beats '1'
print((3, 9) < (3, 14)) # True: element-wise and numeric
print(sys.version_info >= (3, 10))go deeper
Memorise the one-line idiom and be able to write it unprompted: compare sys.version_info against a tuple, with >=, never slice or float the sys.version banner.
Be ready to explain the mechanics: tuple comparison is element-wise, sys.version_info has five fields, and platform.python_version_tuple returns strings so it carries the same lexicographic trap.
Expect to defend where the gate belongs — evaluated once at import rather than in a hot path — and to say which side of it your CI actually exercises, since an untested fallback branch is the real production risk.
Own the policy: which interpreter versions the codebase supports, when the floor moves, and whether version branches are allowed to accumulate at all or must be deleted as the floor rises.
## The object you should be comparing `sys.version_info` is not a plain 3-tuple and not a string. It is a named tuple (a structseq, the same C-level flavour of tuple `os.stat` returns) with five fields: `major`, `minor`, `micro`, `releaselevel` and `serial`. On CPython 3.14.0 final its value is `(3, 14, 0, 'final', 0)`; `releaselevel` is one of `'alpha'`, `'beta'`, `'candidate'` or `'final'`, and `serial` numbers the pre-releases within a level. Because it is a tuple, it compares the way tuples compare: element by element, left to right, and the first pair that differs decides the result. A shorter tuple that is a prefix of a longer one sorts smaller. That is why the canonical gate is written against a two-element tuple: ```python import sys if sys.version_info >= (3, 11): import tomllib ``` `(3, 14, 0, 'final', 0) >= (3, 11)` compares `3 == 3`, then `14 > 11`, and stops with `True`. No parsing, no locale, no formatting assumptions. ## Why the string is the wrong input `sys.version` is the interpreter's display banner: the version, the build date, the compiler, sometimes a newline. It exists to be printed at a REPL prompt or into a bug report, and the standard library documents it as human-readable, which means its exact shape is not something to build logic on. Two failure modes turn up in real code, and both surfaced in a wave when 3.10 shipped: - **Lexicographic ordering.** `"3.9" < "3.14"` evaluates to `False`, because character comparison reaches `'9'` versus `'1'` and stops. A gate written as `sys.version >= "3.10"` therefore claims that 3.9 satisfies it. - **Float coercion.** `float("3.10")` is `3.1`, which is numerically *smaller* than `float("3.9")`. Code shaped like `float(sys.version[:3]) >= 3.10` reads plausibly and is wrong the moment the minor version reaches two digits. The same trap sits one module over: `platform.python_version()` returns a *string* like `'3.14.0'`, and `platform.python_version_tuple()` returns a tuple of *strings* — `('3', '14', '0')` — so comparing that tuple is still lexicographic. If you reach for `platform`, convert to `int` yourself, or just use `sys.version_info`, which is already numeric. ## `sys.hexversion` and when it helps `sys.hexversion` packs the same five fields into a single integer: on 3.14.0 final it is `0x030E00F0`, one byte each for major, minor and micro, then a nibble for the release level (`0xF` meaning final) and a nibble for the serial. It is useful when you want one comparable number — `sys.hexversion >= 0x030B00F0` for "3.11.0 final or later" — and it mirrors the layout CPython's C headers expose to extension code, so build-time and run-time checks can be written the same way. For ordinary Python, the tuple form is more readable and should be the default. ## Getting the comparison itself right - **Use `>=`, never `==`.** `sys.version_info == (3, 11)` is `False` on 3.11.4 and on every later release; a feature gate that pins an exact version breaks on the next patch. - **Compare against the version that introduced the feature**, not the version you happened to test on. - **Do not compare the minor field alone.** `sys.version_info[1] >= 11` is `True` on a hypothetical 4.x line where nothing is guaranteed. - **Mind pre-releases.** Because `releaselevel` is only the fourth field, `sys.version_info >= (3, 14)` is already `True` on a 3.14.0 alpha. The release levels happen to sort correctly alphabetically (`'alpha' < 'beta' < 'candidate' < 'final'`), so if you truly need a shipped release you can compare against `(3, 14, 0, 'final')`. ## When a version check is the right tool at all A version number tells you what the *language release* claims to implement — not that a particular module is installed in this environment, and not which interpreter is running. Prefer probing for the thing you actually need where you can. Version checks stay unavoidable for two cases: behaviour that changed with no importable marker (for example the default `multiprocessing` start method becoming `forkserver` on Unix other than macOS in 3.14), and syntax-level features, which fail at compile time and so can never be caught by a runtime probe. Finally, back the check with a test matrix. A version gate is a branch, and a branch nobody runs on the other side of the comparison is a branch that will be wrong the day someone deploys on the older interpreter.
- What are the five fields of sys.version_info, and how does a beta build compare?They are major, minor, micro, releaselevel and serial — for example (3, 14, 0, 'final', 0). Because releaselevel is only the fourth field, `sys.version_info >= (3, 14)` is already True on a 3.14.0 alpha or beta. The four level names sort correctly alphabetically, so comparing against (3, 14, 0, 'final') excludes pre-releases if that matters.
- When would you reach for sys.hexversion instead of sys.version_info?When you want one integer rather than a tuple: sys.hexversion packs the same five fields into a number, so `sys.hexversion >= 0x030B00F0` means 3.11.0 final or later. It matches the layout used at the C level, which keeps a build-time check and a run-time check written the same way. For readable pure-Python code the tuple stays the better default.
- Why is `sys.version_info == (3, 12)` a bug rather than a strict check?Equality against a two-element tuple is always False — sys.version_info has five fields, so it never equals a 2-tuple. Even written as `sys.version_info[:2] == (3, 12)` it pins the branch to one minor release and silently takes the fallback path on 3.13 and later. Feature gates should be lower bounds written with >=.
Comparing "3.9" with "3.14" as text is like alphabetising house numbers: 9 ends up past 14, and the postman goes to the wrong door.
saying these in an interview costs you the question
- Parses sys.version with split and float, breaking on 3.10
- Compares version strings, so 3.9 looks newer than 3.14
- Uses == on the version tuple, so newer releases fail the gate
- Thinks platform.python_version_tuple returns integers
- Compares only the minor field and ignores the major
- Treats sys.version as a stable machine-readable format