PYTHON / GETTING STARTED
Python versions, PEP 8, and community style
Pick a target Python minor version, gate version-specific code with sys.version_info tuples, and write code that follows PEP 8 naming and layout.
What you will learn
- Compare versions with sys.version_info tuples, never with version strings
- Map a language feature to the minor version that introduced it
- Apply PEP 8 naming: snake_case, CapWords, UPPER_SNAKE constants
- Know that style is checked by tools and humans, not by the interpreter
Understanding Python versions, PEP 8, and community style
CPython ships a new minor version roughly once a year: 3.9, 3.10, 3.11, 3.12, and so on, with patch releases like 3.11.7 fixing bugs inside a minor line. Language features arrive in specific minor versions and never get backported: f-strings in 3.6, the walrus operator := in 3.8, structural pattern matching with match/case in 3.10, tomllib in 3.11. So the useful question is not "am I on Python 3?" but "what is the oldest minor version my code must run on?", because that single number decides which syntax you are allowed to type.
A PEP is a Python Enhancement Proposal, a numbered design document. PEP 8 is the one describing layout and naming for Python source: four spaces per indentation level, snake_case for functions, variables, and modules, CapWords for classes, UPPER_SNAKE for module-level constants, and blank lines separating top-level definitions. Nothing in PEP 8 is enforced by the interpreter; a file full of camelCase functions and 200-character lines runs identically. Enforcement comes from tools like ruff, flake8, and the formatter black, which most projects run in CI.
The mental model is two separate layers. Versions are a hard runtime constraint checked by the parser and importer, which is why sys.version_info is a tuple of integers you can compare element by element, and why the wrong syntax fails before a single line of your module executes. Style is a soft social constraint aimed at the next person reading the diff, which is why PEP 8 itself says consistency inside a project outranks the guide, and to ignore a rule when following it would hurt readability.
import sys
# version_info is a tuple of ints, so it compares element by element
print((3, 10) > (3, 9))
# the same check on text compares characters: '1' comes before '9'
print("3.10" > "3.9")
if sys.version_info >= (3, 8):
feature = "walrus operator"
else:
feature = "no walrus operator"
print(feature)Python versions are enforced by the interpreter and must be compared as integer tuples, while PEP 8 style is enforced only by tools and reviewers.
Worked examples
PEP 8 naming is a convention, not a rule
Shows the three main naming styles PEP 8 asks for and proves that an UPPER_SNAKE constant is not actually constant.
MAX_RETRIES = 3 # UPPER_SNAKE: module-level constant by convention
class RetryPolicy: # CapWords for classes
def wait_seconds(self, attempt): # snake_case for methods
return 2 ** attempt
policy = RetryPolicy()
print([policy.wait_seconds(n) for n in range(MAX_RETRIES)])
MAX_RETRIES = 99 # nothing prevents this
print(MAX_RETRIES)Example explained
Line 1MAX_RETRIES in capitals tells readers not to reassign it; the interpreter attaches no meaning to the case.
Line 2RetryPolicy uses CapWords, which is how readers and linters tell a class apart from a function at a call site.
Line 3wait_seconds returns 2 ** attempt, so attempts 0, 1, 2 give the backoff list [1, 2, 4].
Line 4The reassignment to 99 succeeds and prints, showing the naming rule is enforced by review, not by Python.
Detect the feature, not the version number
Uses an import attempt instead of a version comparison to decide which API is available.
try:
from importlib import metadata
source = "importlib.metadata"
except ImportError:
source = "fallback path"
print(source)
print(hasattr(metadata, "version"))Example explained
Line 1importlib.metadata entered the standard library in 3.8, so on older interpreters the import raises ImportError.
Line 2Catching ImportError adapts to what is actually installed, which is more accurate than hardcoding a version boundary.
Line 3hasattr confirms the specific function you rely on exists, guarding against APIs that changed shape between releases.
Line 4This pattern cannot rescue new syntax: syntax errors happen at compile time, before any except clause runs.
Important notes
The 79-character limit in PEP 8 is guidance; many projects configure 88 or 100 instead, so read the repo config before reformatting anything.
On some systems the bare python command is missing or points at Python 2; use python3 on Linux and macOS, or py -3 on Windows.
Common mistakes
Comparing versions as text, for example sys.version[:3] >= "3.9", which is False on Python 3.10 because "3.1" sorts before "3.9"; the code silently takes the legacy branch.
Wrapping new syntax such as match/case or := in a try/except or an if sys.version_info check: the file fails to compile on the older interpreter with a SyntaxError, so the guard never executes.
Mixing tabs and spaces while trying to reach PEP 8 indentation, which raises TabError: inconsistent use of tabs and spaces in indentation rather than just looking untidy.
Try it yourself
Change, predict, then run
In a browser editor, print sys.version_info[:2], then print both sys.version_info >= (3, 9) and sys.version[:3] >= "3.9", and note under which interpreter versions the two results would disagree.
Open the Python workspaceCheck your understanding
A script uses `if sys.version[:3] >= "3.9":` to enable a newer code path. Why does that check fail on Python 3.10?
- sys.version is a string, so the slice "3.1" is compared character by character and sorts before "3.9"
- sys.version was removed in Python 3.10 and replaced entirely by sys.version_info
- PEP 8 forbids slicing sys.version, so linters rewrite the comparison at import time
- sys.version stores the minor number as a float, which rounds 3.10 down to 3.1
Show answer
sys.version is plain text like "3.10.4 (main, ...)", so [:3] yields "3.1" and string comparison stops at the third character where '1' < '9'. The float option is tempting because "3.1" looks like a truncated number, but no arithmetic happens here at all; the fix is comparing the integer tuple sys.version_info >= (3, 9).