python-expert
Python Expert
Expert Python guidance across scripting, web frameworks, and production systems.
Important — Read Reference Files First
Before writing code, load the reference file that matches the user's task:
| User wants to... | Read first |
|---|---|
| Run a quick script, one-off tool, CLI utility, or use inline dependencies | references/uv-scripts.md |
| Build or modify a Django project (models, views, admin, DRF APIs) | references/django.md |
| Build or modify a Flask or FastAPI project (routes, middleware, APIs) | references/flask-fastapi.md |
If none of those match, use the core principles below directly.
Core Principles (Always Apply)
Modern Python Style (3.10+)
Write Python that uses current idioms. Avoid legacy patterns.
# Type hints on all public functions
def fetch_users(limit: int = 50, active: bool = True) -> list[dict[str, Any]]:
...
# Dataclasses over manual __init__
from dataclasses import dataclass, field
@dataclass
class Config:
host: str = "localhost"
port: int = 8000
tags: list[str] = field(default_factory=list)
# match-case for dispatch
match command:
case "start":
start_server()
case "stop" | "quit":
shutdown()
case _:
print(f"Unknown: {command}")
# f-strings, never .format() or %
msg = f"Processing {count:,} items in {elapsed:.2f}s"
# pathlib, never os.path
from pathlib import Path
config_path = Path.home() / ".config" / "app" / "settings.toml"
Project Structure Defaults
When creating a new Python project (not a uv one-off script), use this layout:
project-name/
├── pyproject.toml # Single source of truth for metadata + deps
├── src/
│ └── project_name/ # Importable package
│ ├── __init__.py
│ └── main.py
├── tests/
│ ├── conftest.py
│ └── test_main.py
└── README.md
Use pyproject.toml as the single config file. Do not create setup.py, setup.cfg, or requirements.txt unless the user specifically asks for them.
Error Handling
Be specific with exceptions. Never use bare except:.
# Good — specific exceptions, useful messages
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
logger.error("Invalid JSON at line %d: %s", e.lineno, e.msg)
raise ValueError(f"Could not parse config: {e}") from e
# Good — custom exceptions for domain logic
class InsufficientFundsError(Exception):
def __init__(self, balance: Decimal, amount: Decimal):
self.balance = balance
self.amount = amount
super().__init__(f"Cannot withdraw {amount}: balance is {balance}")
Testing with pytest
Default to pytest for all testing. Structure tests to mirror source layout.
# Use fixtures for shared setup
import pytest
@pytest.fixture
def db_session():
session = create_test_session()
yield session
session.rollback()
# Parametrize for multiple cases
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("", ""),
("café", "CAFÉ"),
])
def test_uppercase(input, expected):
assert to_upper(input) == expected
# Use tmp_path for file operations
def test_export(tmp_path):
out = tmp_path / "report.csv"
export_report(out)
assert out.read_text().startswith("id,name")
Async Programming
Use async for I/O-bound work. Never use it for CPU-bound work (use multiprocessing instead).
import asyncio
import httpx
async def fetch_all(urls: list[str]) -> list[dict]:
async with httpx.AsyncClient() as client:
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks, return_exceptions=True)
return [
r.json() for r in responses
if not isinstance(r, Exception)
]
Prefer httpx over aiohttp — it has sync and async APIs with the same interface.
Dependencies and Packaging
For full projects, use pyproject.toml:
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"httpx>=0.27",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff"]
[tool.ruff]
line-length = 100
For quick scripts, use uv inline metadata — see references/uv-scripts.md.
Logging (not print)
Use structured logging for anything beyond throwaway scripts:
import logging
logger = logging.getLogger(__name__)
# Configure once at entry point
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
Common Pitfalls to Catch
When reviewing or writing Python code, watch for:
- Mutable default arguments (
def f(items=[])— useNone+ conditional) - Late binding closures in loops (use
functools.partialor default args) - Missing
if __name__ == "__main__":guard in scripts - Using
datetime.now()without timezone (usedatetime.now(timezone.utc)) - Bare
except:orexcept Exception:swallowing errors silently os.pathwhenpathlibwould be clearerrequestsin async contexts (usehttpxinstead)
Troubleshooting Approach
When the user has an error or unexpected behavior:
- Read the full traceback — Python errors are specific and descriptive
- Identify the exception type and the failing line
- Check types with
type()/isinstance()if the error suggests a type mismatch - Reproduce minimally — strip away unrelated code
- Verify Python version compatibility (
python --version) - Check the virtual environment is activated and deps are installed
- Use
python -m pytest -x --tb=shortfor quick test feedback - Use
ruff check .for lint issues instead of pylint/flake8 (faster, more modern)