Python Fundamentals Best Practices
Core habits that keep Python code readable, correct, and maintainable before you reach for frameworks or data tools. Treat this as a onboarding checklist for every new module or repo.
How to Use This List
- Review when starting a new package or onboarding a teammate.
- Apply during code review for files touching fundamentals (I/O, imports, functions).
- Revisit after incidents rooted in mutability, encoding, or environment drift.
- Pair with ruff/mypy CI so rules become automatic, not aspirational.
A - Environment and Tooling
- Create a virtual environment before installing third-party packages. Never pollute system Python (PEP 668).
- Pin dependencies with
pyproject.tomland a lockfile (uv.lock). Reproducible CI and clones. - Target Python 3.14+ features only when the runtime pin allows. Document
requires-pythoninpyproject.toml. - Run
ruff checkandruff formaton every PR. Fast style and lint feedback. - Enable mypy or pyright incrementally on new modules. Types pay off at API boundaries.
B - Code Style and Readability
- Follow PEP 8 naming:
snake_casefunctions,PascalCaseclasses,UPPERconstants. - Prefer f-strings over
%formatting andformat()for new code. Clearer and faster. - Use
pathlib.Pathinstead ofos.pathstring juggling. Cross-platform safe joins. - Keep functions short with one clear responsibility. Extract when nesting exceeds two levels.
- Write docstrings on public modules, classes, and functions. One-line summary plus params when non-obvious.
C - Data and Correctness
- Never use mutable default arguments (
def f(x=[])). UseNoneand assign inside. - Use
Decimalfor money, not binary float. Avoid0.1 + 0.2surprises in billing. - Specify
encoding="utf-8"on all text file I/O. Prevent platform-default bugs. - Copy nested structures with
copy.deepcopywhen independence matters. Shallow copies share inner lists. - Use
isinstancefor type checks, nottype(x) is Clson subclasses. Polymorphism-friendly.
D - Control Flow and APIs
- Prefer comprehensions or generators over manual
appendloops when transforming sequences. Readable and often faster. - Use
match/casefor structural branching on dicts and dataclasses. Cleaner than deepifchains. - Expose keyword-only options after
*in public APIs. Prevents ambiguous call sites. - Guard scripts with
if __name__ == "__main__":. Importable modules without side effects. - Run packages with
python -m package.module. Correct relative imports and__package__.
E - Imports and Layout
- Use absolute imports in application code (
from myapp.utils import x). Clearer than relative in large trees. - Break circular imports with lazy imports or shared
typesmodules. Fail fast at design time when possible. - Avoid shadowing stdlib module names (
json.py,types.py). Breaks imports mysteriously. - Adopt
src/layout for installable packages. Prevents accidental imports from repo root. - Keep import-time side effects minimal. No network or DB at module load in libraries.
FAQs
Should beginners memorize every PEP 8 rule?
No - configure ruff and focus on readability. Automate formatting debates away.
When is global state OK?
Module-level constants and cached read-only config are fine. Mutable globals complicate tests - prefer injection.
Are type hints required on scripts?
Optional for throwaway scripts. Add them when the script grows or becomes a package.
match or if/elif?
match for structure (dict keys, typed events). if for simple scalar conditions.
uv or poetry for new projects?
This cookbook pins uv 0.6+ as the default fast path. Poetry remains valid for teams already standardized on it.
How strict should mypy be?
Start with new modules at strict optional flags; widen gradually. Pydantic models at HTTP boundaries first.
Is list comprehension always better than a loop?
Better for map/filter transforms. Use a for loop when side effects or complex branching dominate.
Should I use print for logging?
print is fine for CLI UX. Services should use logging with structured fields - see errors-logging section.
How do I teach truthiness?
Explicit comparisons (is None, len(x) == 0) when falsy-but-valid values like 0 matter.
What is the single highest-impact habit?
Isolated environments with locked dependencies - everything else fails without reproducible installs.
Related
- Python Basics - runnable intro tour
- Variables, Types & Dynamic Typing - reference semantics
- Virtual Environments Quickstart - uv workflow
- Type Hints Basics - static typing entry
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.