Skip to Content

Code Style

Consistent naming, complete type annotations, auto-formatting, and Google-style docstrings.

Naming Conventions

Variables and Functions

# snake_case — descriptive names user_profile = get_user_profile(user_id) validated_input = validate_and_transform(raw_input) is_active: bool = check_status(account) # Boolean prefixes: is_, has_, can_, should_ is_valid: bool has_permission: bool can_edit: bool should_retry: bool

Classes

class UserService: # PascalCase, noun + role class AsyncUserService: # Async prefix for async variant class BaseRepository: # Base prefix for base classes class UserRepository: # Implementation name # Suffixes indicate purpose class UserValidator: # Validation class UserFactory: # Object creation class UserHandler: # Event/request handling

Constants and Enums

# SCREAMING_SNAKE_CASE for constants MAX_RETRIES: int = 3 DEFAULT_TIMEOUT: int = 30 API_BASE_URL: str = "https://api.example.com" class UserRole(str, Enum): ADMIN = "admin" USER = "user" GUEST = "guest"

Private vs Public

class UserService: def get_user(self): # Public API ... def _fetch_from_cache(self): # Private implementation ... # Module-level private _internal_cache: dict = {} def _helper_function() -> None: ...

Type Annotations

Modern Syntax (Python 3.10+)

# Use | instead of Union def get_user(user_id: int) -> User | None: ... # Use built-in types instead of typing imports def process(items: list[Item]) -> dict[str, Item]: ... # Complete annotations on all parameters and returns def process_users( users: list[User], filter_fn: Callable[[User], bool], max_results: int = 100, ) -> list[ProcessedUser]: ... # Class attributes typed class UserService: _config: Config _cache: dict[int, User] _client: httpx.Client | None def __init__(self, config: Config) -> None: self._config = config self._cache = {} self._client = None

Formatting

Black (88 chars)

# pyproject.toml [tool.black] line-length = 88 target-version = ['py310']

Code Patterns

# Function arguments — one per line def create_user( name: str, email: str, role: UserRole = UserRole.USER, metadata: dict[str, str] | None = None, ) -> User: ... # Method chaining — align at dot result = ( QueryBuilder() .select("id", "name") .from_table("users") .where("active", True) .limit(100) .execute() ) # Conditions — align logically if ( user.is_active and user.has_permission("admin") and not user.is_suspended ): perform_action(user)

Import Organization (isort)

# 1. Standard library import asyncio import logging from datetime import datetime # 2. Third-party import httpx from pydantic import BaseModel, Field # 3. Local from .models import User from .services import UserService
[tool.isort] profile = "black" line_length = 88 known_first_party = ["src"]

Documentation

Google-Style Docstrings

def create_user(self, data: UserCreate, validate: bool = True) -> User: """Create a new user. Args: data: User creation data including name and email. validate: Whether to run validation rules. Returns: The created user with assigned ID. Raises: ValidationError: If data fails validation. ConflictError: If email already exists. """

Inline Comments

# GOOD — explain why, not what # Rate limit: max 10 requests per second per user if request_count > 10: await asyncio.sleep(1) # BAD — stating the obvious # Increment counter counter += 1

Function Guidelines

Max 20 Lines

# BAD — monolithic function def process_everything(data): # 50+ lines of validation, transformation, persistence ... # GOOD — composed functions def process_data(data: InputModel) -> ProcessedModel: validated = validate(data) transformed = transform(validated) return persist(transformed)

Guard Clauses

# BAD — deeply nested def process_order(order): if order: if order.items: if order.customer: # actual logic ... # GOOD — guard clauses, flat logic def process_order(order: Order) -> ProcessedOrder: if not order: raise ValidationError("Order required") if not order.items: raise ValidationError("Order must have items") if not order.customer: raise ValidationError("Order must have customer") return _execute_processing(order)

Class Guidelines

Max 200 Lines, Composition Over Inheritance

# BAD — deep inheritance class UserCachedAuthService(CachedService, AuthService, BaseService): ... # GOOD — composition class UserService: def __init__( self, auth: Authenticator, cache: Cache, repo: UserRepository, ): self._auth = auth self._cache = cache self._repo = repo

__slots__ for Data Classes

class UserData: __slots__ = ("id", "name", "email") def __init__(self, id: int, name: str, email: str): self.id = id self.name = name self.email = email # ~100 bytes per instance vs ~300 without __slots__

Tooling

# pyproject.toml [tool.mypy] python_version = "3.10" strict = true disallow_untyped_defs = true warn_return_any = true [tool.flake8] max-line-length = 88 max-complexity = 10 extend-ignore = ["E203", "W503"] [tool.bandit] exclude_dirs = ["tests", "venv"]

Rules

  1. snake_case for variables/functions, PascalCase for classes, SCREAMING_SNAKE for constants
  2. Complete type annotations — every parameter, every return, no Any
  3. Black + isort — auto-format, no debates
  4. Functions < 20 lines — split when exceeded
  5. Classes < 200 lines — extract when exceeded
  6. Guard clauses — early returns, flat logic
  7. Composition over inheritance — inject dependencies
  8. Google-style docstrings — on all public functions
Last updated on