Skip to content

Python Coding Standards ​

This page was generated by AI and has been manually reviewed and edited.

This document standardizes code style, toolchain, and commit workflows for Python projects, reducing collaboration overhead and improving maintainability.

1. Code Organization ​

1.1 Module Design ​

  • Each module has a single responsibility; avoid cramming too many features into one file
  • Split packages by functionality; avoid catch-all packages like util/common/misc
  • Types and functions with similar functionality go in the same module
  • Avoid circular imports; mark internal implementations as private with a _ prefix

1.2 File Organization ​

  • Order within a file: shebang → encoding declaration → module docstring → import → constants → variables → functions → classes
  • One file focuses on one core class or feature group
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""User service module, providing user-related business logic and data access."""

from __future__ import annotations

import logging
from datetime import datetime

from pydantic import BaseModel

from .repository import UserRepository

MAX_RETRY_COUNT = 3

logger = logging.getLogger(__name__)


class User(BaseModel):
    id: int
    name: str


def get_user(user_id: int) -> User | None:
    ...

2. Naming Conventions ​

2.1 Basic Rules ​

  • Modules/packages: short lowercase names, may use underscores as separators (user_service.py)
  • Classes: PascalCase (UserService, OrderProcessor)
  • Functions/methods/variables: snake_case (get_user, item_count)
  • Constants: UPPER_SNAKE_CASE (MAX_RETRY_COUNT, API_BASE_URL)

2.2 Private Members ​

  • Non-public attributes use a single underscore prefix (_internal_method, _cache)
  • Avoid double underscore prefixes unless name mangling is genuinely needed to prevent subclass conflicts

2.3 Boolean Variables ​

  • Use is_ / has_ / can_ prefixes to indicate boolean meaning
python
is_active: bool
has_permission: bool
can_edit: bool

3. Type Annotations ​

3.1 Basic Requirements ​

  • All new code must include type annotations
  • Public interfaces must provide complete function signature annotations
  • Python 3.10+ use X | None instead of Optional[X]
  • Python 3.9+ use built-in generics list[X], dict[K, V]; deprecate typing.List, typing.Dict

3.2 Complex Types ​

  • Prefer TypedDict, Protocol, dataclass for complex structures
  • Avoid excessive dict[str, Any]; use structured types instead
python
from dataclasses import dataclass
from typing import TypedDict


@dataclass(slots=True)
class User:
    id: int
    name: str


class UserDict(TypedDict):
    id: int
    name: str


def find_user_name(users: list[User], user_id: int) -> str | None:
    for user in users:
        if user.id == user_id:
            return user.name
    return None

3.3 Type Checking ​

  • Recommend using mypy --strict mode
bash
mypy --strict src/

4. Import Conventions ​

4.1 Import Order ​

  • Standard library → third-party libraries → local modules, separated by blank lines
  • Each import on its own line
python
import os
import sys
from pathlib import Path

from pydantic import BaseModel
from sqlalchemy import select

from .models import User
from .service import UserService

4.2 Prohibited Actions ​

  • No from module import * (except for modules with explicitly declared __all__)
  • No circular imports
  • No relative imports using .. that cross package boundaries

5. Strings and Formatting ​

5.1 String Usage ​

  • Prefer f-strings (Python 3.6+)
  • Triple-quoted strings for multi-line strings
  • Avoid string concatenation in loops; use "".join() or StringIO
python
name = "Alice"
msg = f"Hello, {name}!"

query = """
    SELECT id, name
    FROM users
    WHERE active = true
"""

5.2 Docstring ​

  • Modules, classes, and public functions must have docstrings
  • Use triple quotes; first line is a brief description, followed by a blank line and detailed explanation
  • Recommend Google or NumPy style
python
def get_user(user_id: int) -> User | None:
    """Get a user by ID.

    Args:
        user_id: The unique identifier of the user.

    Returns:
        The matching User object, or None if not found.

    Raises:
        ValueError: Raised when user_id is not a positive integer.
    """
    if user_id <= 0:
        raise ValueError("user_id must be positive")
    ...

6. Error Handling ​

6.1 Basic Principles ​

  • Catch specific exceptions; no bare except: or except Exception:
  • Custom exceptions inherit from Exception and provide meaningful error messages
  • Never silently swallow exceptions in except blocks

6.2 Exception Design ​

python
class UserNotFoundError(Exception):
    """Exception raised when a user is not found."""

    def __init__(self, user_id: int):
        self.user_id = user_id
        super().__init__(f"User {user_id} not found")


class ValidationError(Exception):
    """Exception raised for data validation errors."""
    ...

6.3 Resource Management ​

  • Use context managers (with) for managing file, connection, and other resources
  • Custom resources implement __enter__ and __exit__
python
with open("data.txt") as f:
    content = f.read()

7. Functions and Methods ​

7.1 Function Design ​

  • Keep functions as small as possible; recommend no more than 50 lines
  • One function does one thing
  • When there are too many parameters, consider encapsulating them with dataclass or TypedDict

7.2 Default Parameters ​

  • Default parameter values must be immutable; never def f(x=[]) or def f(x={})
python
# Correct
def append_item(item: str, items: list[str] | None = None) -> list[str]:
    if items is None:
        items = []
    items.append(item)
    return items

# Incorrect
def append_item(item: str, items: list[str] = []) -> list[str]:
    items.append(item)
    return items

7.3 Function Style ​

  • Use keyword arguments to improve readability
  • Prefer def for defining functions; never assign a lambda to a variable
python
# Correct
def is_active(user: User) -> bool:
    return user.status == "active"

# Incorrect
is_active = lambda user: user.status == "active"

8. Class Design ​

8.1 Design Principles ​

  • Use @property instead of Java-style getters/setters
  • Use @dataclass to simplify data classes
  • Prefer composition over inheritance
  • Ensure MRO is clear and understandable when using multiple inheritance

8.2 Special Methods ​

  • __str__ is user-facing, returning a highly readable description
  • __repr__ is developer-facing, ideally returning an expression that can reconstruct the object
python
from dataclasses import dataclass


@dataclass(slots=True)
class User:
    id: int
    name: str
    email: str

    @property
    def display_name(self) -> str:
        return self.name or self.email

    def __str__(self) -> str:
        return f"User({self.display_name})"

    def __repr__(self) -> str:
        return f"User(id={self.id!r}, name={self.name!r}, email={self.email!r})"

9. Concurrency and Async ​

9.1 Async Programming ​

  • IO-intensive tasks should prefer async / await
  • Use asyncio.gather for concurrent execution of multiple coroutines
  • Use asyncio.to_thread to wrap synchronous blocking calls
python
import asyncio


async def fetch_all(urls: list[str]) -> list[str]:
    async def fetch(url: str) -> str:
        ...
        return ""

    return await asyncio.gather(*(fetch(url) for url in urls))

9.2 Thread Safety ​

  • Use threading.Lock to protect shared state in multi-threaded scenarios
  • Use queue.Queue for inter-thread communication
  • Never use time.sleep in async code; use await asyncio.sleep

10. Testing Conventions ​

10.1 Framework and Organization ​

  • Use pytest as the testing framework
  • Test file naming: test_*.py; test functions prefixed with test_
  • One test verifies one primary behavior
  • Bug fixes must include test cases

10.2 Testing Practices ​

  • Use fixtures instead of setUp/tearDown
  • Use pytest.mark.parametrize for parameterized tests
  • Mock external dependencies using unittest.mock or pytest-mock
python
import pytest
from unittest.mock import Mock

from .service import UserService


@pytest.fixture
def user_service() -> UserService:
    repo = Mock()
    return UserService(repo)


def test_get_user_returns_none_when_not_found(user_service: UserService) -> None:
    user_service.repo.find_by_id.return_value = None
    assert user_service.get_user(1) is None


@pytest.mark.parametrize(
    ("user_id", "expected"),
    [(0, False), (-1, False), (1, True)],
)
def test_valid_user_id(user_id: int, expected: bool) -> None:
    assert is_valid_user_id(user_id) == expected

11. Toolchain and Formatting ​

  • Python: 3.13+
  • Package management: uv
  • Formatting and linting: ruff
  • Type checking: ty or mypy
  • Testing: pytest
  • Pre-commit hooks: pre-commit

11.2 Common Commands ​

bash
ruff format
ruff check
ruff check --fix
ty check
pytest
  • ruff format handles code formatting
  • ruff check handles style and common issue checks
  • --fix can auto-fix some issues
  • All checks must pass in CI

12. Prohibited Actions ​

  • No bare except: or except Exception:
  • No mutable default parameters (def f(x=[]))
  • No modifying global variables inside functions
  • No hardcoding sensitive information such as keys or credentials
  • No using assert for business logic validation
  • No from module import *
  • No circular imports
  • No assigning a lambda to a variable
  • No silently swallowing exceptions
  • No using time.sleep in async code