Skip to content

Python Documentation Guide

Google Style + Sphinx + Type Hints für PySide6 Projekte


Inhaltsverzeichnis

  1. Setup & Installation
  2. Google Style Docstrings
  3. Type Hints Best Practices
  4. PySide6-Spezifische Dokumentation
  5. Sphinx Konfiguration
  6. Auto-Documentation Setup
  7. Read the Docs Integration
  8. PyCharm Integration
  9. Best Practices

Setup & Installation

Projekt-Struktur

myproject/
├── src/
│   └── myapp/
│       ├── __init__.py
│       ├── models/
│       ├── views/
│       └── controllers/
├── docs/
│   ├── source/
│   │   ├── conf.py
│   │   ├── index.rst
│   │   └── api/
│   ├── build/
│   └── Makefile
├── tests/
├── pyproject.toml
└── README.md

Installation

pyproject.toml:

[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
    "PySide6>=6.5.0",
]

[project.optional-dependencies]
docs = [
    "sphinx>=7.0.0",
    "sphinx-rtd-theme>=2.0.0",
    "sphinx-autodoc-typehints>=1.24.0",
    "myst-parser>=2.0.0",  # Optional: Markdown support
]
dev = [
    "mypy>=1.5.0",
    "black>=23.0.0",
    "ruff>=0.1.0",
]

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

Installation:

# Projekt installieren
pip install -e .

# Docs dependencies
pip install -e ".[docs]"

# Dev dependencies
pip install -e ".[dev]"

Sphinx Initialisierung

# Docs-Verzeichnis erstellen
mkdir docs
cd docs

# Sphinx quickstart
sphinx-quickstart

# Antworten:
# > Separate source and build directories (y/n) [n]: y
# > Project name: My App
# > Author name(s): Your Name
# > Project release []: 0.1.0
# > Project language [en]: en

Resultierende Struktur:

docs/
├── source/
│   ├── conf.py
│   ├── index.rst
│   ├── _static/
│   └── _templates/
├── build/
└── Makefile


Google Style Docstrings

Modul-Docstring

# src/myapp/models/data_model.py
"""
Data Model Module.

This module contains the core data model classes for the application.
It provides the Model component of the MVP architecture and handles
all data persistence and business logic.

Example:
    Basic usage of the DataModel::

        from myapp.models.data_model import DataModel

        model = DataModel()
        model.add_item("Item 1")
        model.save("data.json")

Todo:
    * Add support for database backends
    * Implement data validation
    * Add undo/redo functionality

.. _Google Python Style Guide:
   https://google.github.io/styleguide/pyguide.html
"""

from typing import List, Optional, Dict, Any
from pathlib import Path
from PySide6.QtCore import QObject, Signal

Klassen-Docstring

class DataModel(QObject):
    """
    Core data model for application data.

    This class manages the application's data and provides a clean interface
    for data manipulation. It follows Qt's signal/slot mechanism for
    notifying views of data changes.

    The model maintains a list of items and tracks modification state.
    Data can be persisted to JSON files.

    Attributes:
        items (List[str]): List of data items (read-only property).
        modified (bool): True if data has been modified since last save.
        file_path (Optional[Path]): Path to currently loaded file.

    Signals:
        data_changed(list): Emitted when items are added, removed, or modified.
            Args:
                list: The updated list of items.

        error_occurred(str): Emitted when an error occurs.
            Args:
                str: Error message describing what went wrong.

        file_loaded(Path): Emitted when a file is successfully loaded.
            Args:
                Path: Path to the loaded file.

    Example:
        Create a model and add items::

            model = DataModel()
            model.data_changed.connect(lambda items: print(f"Items: {items}"))
            model.add_item("First Item")
            model.save(Path("data.json"))

    Note:
        This class is thread-safe for signal emissions but not for
        direct data access. Use signals for cross-thread communication.

    See Also:
        :class:`DataView`: The view component that displays this model.
        :class:`DataController`: The controller that coordinates model and view.
    """

    # Type annotations for signals (for documentation)
    data_changed: Signal = Signal(list)
    error_occurred: Signal = Signal(str)
    file_loaded: Signal = Signal(Path)

    def __init__(self, parent: Optional[QObject] = None):
        """
        Initialize the data model.

        Args:
            parent: Optional parent QObject for memory management.
                If provided, this object will be deleted when parent is deleted.

        Example:
            >>> model = DataModel()
            >>> model.items
            []
        """
        super().__init__(parent)
        self._items: List[str] = []
        self._modified: bool = False
        self._file_path: Optional[Path] = None

Methoden-Docstring: Standard

    def add_item(self, item: str) -> None:
        """
        Add an item to the model.

        The item is appended to the internal list and the data_changed
        signal is emitted. Marks the model as modified.

        Args:
            item: The item to add. Must not be empty or already exist.

        Raises:
            ValueError: If item is empty string.
            ValueError: If item already exists in the list.

        Example:
            >>> model = DataModel()
            >>> model.add_item("Task 1")
            >>> model.items
            ['Task 1']

        Note:
            This method is thread-safe via Qt's signal mechanism.
        """
        if not item:
            raise ValueError("Item cannot be empty")

        if item in self._items:
            raise ValueError(f"Item '{item}' already exists")

        self._items.append(item)
        self._modified = True
        self.data_changed.emit(self._items.copy())

Methoden-Docstring: Mit Type Hints

    def load(self, file_path: Path) -> bool:
        """
        Load data from a JSON file.

        Loads items from the specified JSON file and replaces current data.
        Emits file_loaded signal on success or error_occurred on failure.

        Args:
            file_path: Path to the JSON file to load.

        Returns:
            True if loading was successful, False otherwise.

        Raises:
            FileNotFoundError: If the file does not exist.
            json.JSONDecodeError: If the file contains invalid JSON.
            PermissionError: If the file cannot be read due to permissions.

        Example:
            Load data from a file::

                model = DataModel()
                try:
                    success = model.load(Path("data.json"))
                    if success:
                        print(f"Loaded {len(model.items)} items")
                except FileNotFoundError:
                    print("File not found")

        Warning:
            This method will discard any unsaved changes. Make sure to
            call :meth:`save` before loading if needed.

        See Also:
            :meth:`save`: Save data to a file.
        """
        try:
            with open(file_path, 'r') as f:
                data = json.load(f)

            self._items = data.get('items', [])
            self._file_path = file_path
            self._modified = False

            self.file_loaded.emit(file_path)
            self.data_changed.emit(self._items.copy())
            return True

        except Exception as e:
            self.error_occurred.emit(str(e))
            return False

Property-Docstring

    @property
    def items(self) -> List[str]:
        """
        Get a copy of all items.

        Returns:
            A copy of the items list. Modifications to this list
            will not affect the internal data.

        Example:
            >>> model = DataModel()
            >>> model.add_item("Item 1")
            >>> items = model.items
            >>> items.append("Item 2")  # Does not affect model
            >>> model.items
            ['Item 1']

        Note:
            Returns a copy to prevent external modification of internal state.
        """
        return self._items.copy()

    @property
    def modified(self) -> bool:
        """
        Check if data has been modified since last save.

        Returns:
            True if data has been modified, False otherwise.

        Example:
            >>> model = DataModel()
            >>> model.modified
            False
            >>> model.add_item("Item")
            >>> model.modified
            True
        """
        return self._modified

Komplexe Beispiele

    def filter_items(
        self,
        predicate: Callable[[str], bool],
        case_sensitive: bool = True
    ) -> List[str]:
        """
        Filter items using a predicate function.

        Args:
            predicate: Function that takes an item and returns True to include it.
            case_sensitive: If False, convert items to lowercase before filtering.
                Defaults to True.

        Returns:
            List of items that match the predicate.

        Example:
            Filter items containing 'test'::

                model.add_item("Test 1")
                model.add_item("Test 2")
                model.add_item("Other")

                # Case sensitive
                result = model.filter_items(lambda x: 'Test' in x)
                # result: ['Test 1', 'Test 2']

                # Case insensitive
                result = model.filter_items(
                    lambda x: 'test' in x,
                    case_sensitive=False
                )
                # result: ['Test 1', 'Test 2']

        Note:
            This method does not modify the original list.
        """
        items = self._items if case_sensitive else [i.lower() for i in self._items]
        return [item for item in items if predicate(item)]

Type Hints Best Practices

Import Standards

from typing import (
    List,           # List[int], List[str]
    Dict,           # Dict[str, int]
    Set,            # Set[str]
    Tuple,          # Tuple[int, str]
    Optional,       # Optional[str] = Union[str, None]
    Union,          # Union[int, str]
    Any,            # Any type (use sparingly!)
    Callable,       # Callable[[int, str], bool]
    TypeVar,        # Generic types
    Generic,        # Generic base class
    Protocol,       # Structural subtyping
    cast,           # Type casting
    overload,       # Function overloading
)
from pathlib import Path
from PySide6.QtCore import QObject, Signal

Type Aliases

"""Type aliases for better readability."""

# Simple aliases
ItemID = int
ItemName = str
ItemDict = Dict[ItemID, ItemName]

# Complex types
CallbackType = Callable[[str, int], bool]
SignalType = Signal

# Generic aliases
T = TypeVar('T')
ItemList = List[T]

# Usage
def process_items(items: ItemList[str]) -> ItemDict:
    """Process items and return mapping."""
    return {idx: name for idx, name in enumerate(items)}

PySide6 Type Hints

from PySide6.QtCore import QObject, Signal, Slot, QTimer
from PySide6.QtWidgets import QWidget, QPushButton, QVBoxLayout
from typing import Optional

class MyWidget(QWidget):
    """Custom widget with proper type hints."""

    # Signal with type annotation (for documentation)
    value_changed: Signal = Signal(int)

    def __init__(self, parent: Optional[QWidget] = None):
        """
        Initialize widget.

        Args:
            parent: Parent widget for Qt ownership.
        """
        super().__init__(parent)
        self._value: int = 0
        self._button: QPushButton = QPushButton("Click")
        self._timer: Optional[QTimer] = None

    @Slot(int)
    def set_value(self, value: int) -> None:
        """
        Set the widget value.

        Args:
            value: New value to set.
        """
        self._value = value
        self.value_changed.emit(value)

    def get_value(self) -> int:
        """
        Get current value.

        Returns:
            Current widget value.
        """
        return self._value

Generic Types

from typing import TypeVar, Generic, List

T = TypeVar('T')

class DataContainer(Generic[T]):
    """
    Generic container for any data type.

    Type Parameters:
        T: The type of items stored in this container.

    Example:
        >>> container = DataContainer[str]()
        >>> container.add("item")
        >>> container.items
        ['item']
    """

    def __init__(self):
        self._items: List[T] = []

    def add(self, item: T) -> None:
        """
        Add an item to the container.

        Args:
            item: Item to add.
        """
        self._items.append(item)

    @property
    def items(self) -> List[T]:
        """Get all items."""
        return self._items.copy()

Union Types (Python 3.10+)

# Old style
from typing import Union, Optional

def process(value: Union[int, str]) -> Optional[bool]:
    """Process value."""
    pass

# New style (Python 3.10+)
def process(value: int | str) -> bool | None:
    """Process value."""
    pass

Literal Types

from typing import Literal

ConnectionType = Literal['auto', 'direct', 'queued', 'blocking']

def connect_signal(
    signal: Signal,
    slot: Callable,
    connection_type: ConnectionType = 'auto'
) -> None:
    """
    Connect signal to slot.

    Args:
        signal: Signal to connect.
        slot: Slot function to call.
        connection_type: Type of connection. One of:
            - 'auto': Automatic (default)
            - 'direct': Direct connection
            - 'queued': Queued connection
            - 'blocking': Blocking queued connection
    """
    pass

PySide6-Spezifische Dokumentation

Signals Dokumentieren

class Worker(QObject):
    """
    Background worker thread.

    This worker performs long-running operations in a separate thread
    and reports progress via signals.

    Signals:
        progress(int): Emitted periodically during work.
            Args:
                int: Progress percentage (0-100).

        finished(dict): Emitted when work is complete.
            Args:
                dict: Results dictionary with keys:
                    - 'status' (str): 'success' or 'error'
                    - 'data' (Any): Result data
                    - 'duration' (float): Execution time in seconds

        error(str, Exception): Emitted when an error occurs.
            Args:
                str: Error message.
                Exception: The exception that occurred.

    Example:
        Setup and run a worker::

            worker = Worker()
            thread = QThread()

            worker.moveToThread(thread)
            worker.finished.connect(thread.quit)
            worker.progress.connect(lambda p: print(f"Progress: {p}%"))

            thread.started.connect(worker.do_work)
            thread.start()
    """

    progress: Signal = Signal(int)
    finished: Signal = Signal(dict)
    error: Signal = Signal(str, Exception)

Slots Dokumentieren

class DataView(QWidget):
    """View for displaying data."""

    @Slot(list)
    def update_display(self, items: List[str]) -> None:
        """
        Update the display with new items.

        This slot is connected to the model's data_changed signal
        and updates the UI accordingly.

        Args:
            items: List of items to display.

        Note:
            This method runs in the GUI thread and should complete quickly.
            For expensive operations, use a worker thread.

        See Also:
            :class:`DataModel`: Emits the data_changed signal.
        """
        self._list_widget.clear()
        self._list_widget.addItems(items)

    @Slot(str)
    def show_error(self, message: str) -> None:
        """
        Display an error message.

        Args:
            message: Error message to display.
        """
        QMessageBox.critical(self, "Error", message)

Model/View Pattern Dokumentieren

from PySide6.QtCore import QAbstractListModel, QModelIndex, Qt
from typing import Any

class TodoListModel(QAbstractListModel):
    """
    Model for a todo list.

    This model provides data for Qt's view classes (QListView, QTableView, etc.)
    following Qt's Model/View architecture.

    The model stores todo items as strings and supports add/remove operations.

    Data Roles:
        Qt.ItemDataRole.DisplayRole: Item text for display.
        Qt.ItemDataRole.EditRole: Item text for editing.
        Qt.ItemDataRole.CheckStateRole: Todo completion state.

    Example:
        Use with a QListView::

            model = TodoListModel()
            model.add_todo("Buy milk")
            model.add_todo("Write docs")

            view = QListView()
            view.setModel(model)

    See Also:
        https://doc.qt.io/qt-6/model-view-programming.html
    """

    def __init__(self, todos: Optional[List[str]] = None):
        """
        Initialize the todo model.

        Args:
            todos: Initial list of todos. Defaults to empty list.
        """
        super().__init__()
        self._todos = todos or []

    def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
        """
        Return number of rows in the model.

        Args:
            parent: Parent index (unused for list models).

        Returns:
            Number of todo items.
        """
        return len(self._todos)

    def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
        """
        Get data for the specified index and role.

        Args:
            index: Model index to retrieve data for.
            role: Data role (DisplayRole, EditRole, etc.).

        Returns:
            Data for the specified role, or None if invalid.

        Note:
            This method is called by Qt views to retrieve display data.
        """
        if not index.isValid():
            return None

        if role == Qt.ItemDataRole.DisplayRole:
            return self._todos[index.row()]

        return None

Sphinx Konfiguration

docs/source/conf.py

# Configuration file for the Sphinx documentation builder.
# Full list: https://www.sphinx-doc.org/en/master/usage/configuration.html

import os
import sys
from pathlib import Path

# -- Path setup --------------------------------------------------------------
# Add project source to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'src'))

# -- Project information -----------------------------------------------------
project = 'My App'
copyright = '2025, Your Name'
author = 'Your Name'
release = '0.1.0'
version = '0.1'

# -- General configuration ---------------------------------------------------
extensions = [
    # Sphinx built-in
    'sphinx.ext.autodoc',           # Auto-generate from docstrings
    'sphinx.ext.napoleon',          # Google/NumPy style support
    'sphinx.ext.viewcode',          # [source] links
    'sphinx.ext.intersphinx',       # Link to other projects
    'sphinx.ext.todo',              # TODO directives
    'sphinx.ext.coverage',          # Documentation coverage
    'sphinx.ext.githubpages',       # .nojekyll for GitHub Pages

    # Third-party
    'sphinx_rtd_theme',             # Read the Docs theme
    'sphinx_autodoc_typehints',     # Better type hints
]

templates_path = ['_templates']
exclude_patterns = []

# Language
language = 'en'

# -- Options for HTML output -------------------------------------------------
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']

# Theme options
html_theme_options = {
    'navigation_depth': 4,
    'collapse_navigation': False,
    'sticky_navigation': True,
    'includehidden': True,
    'titles_only': False,
    'logo_only': False,
}

# -- Extension configuration -------------------------------------------------

# Napoleon settings (Google/NumPy style)
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = True
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = True
napoleon_use_admonition_for_examples = False
napoleon_use_admonition_for_notes = False
napoleon_use_admonition_for_references = False
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
napoleon_preprocess_types = False
napoleon_type_aliases = None
napoleon_attr_annotations = True

# Autodoc settings
autodoc_default_options = {
    'members': True,
    'member-order': 'bysource',
    'special-members': '__init__',
    'undoc-members': True,
    'exclude-members': '__weakref__'
}
autodoc_typehints = 'description'
autodoc_typehints_description_target = 'all'

# Type hints settings
set_type_checking_flag = True
typehints_fully_qualified = False
always_document_param_types = True
typehints_document_rtype = True

# Intersphinx mapping
intersphinx_mapping = {
    'python': ('https://docs.python.org/3', None),
    'PySide6': ('https://doc.qt.io/qtforpython-6/', None),
}

# TODO extension
todo_include_todos = True

# -- Custom CSS/JS -----------------------------------------------------------
html_css_files = [
    'custom.css',
]

docs/source/_static/custom.css

/* Custom CSS for documentation */

/* Make code blocks more readable */
.highlight {
    background-color: #f8f9fa;
}

/* Better spacing for parameters */
dl.field-list > dt {
    font-weight: bold;
    padding-bottom: 5px;
}

/* Signal/Slot styling */
.sig-name {
    font-family: 'Consolas', 'Monaco', monospace;
}

/* Example boxes */
div.admonition.example {
    background-color: #e7f3ff;
    border-left: 4px solid #2196F3;
}

Auto-Documentation Setup

docs/source/index.rst

Welcome to My App's Documentation!
===================================

My App is a PySide6-based application following the MVP architecture pattern.
This documentation covers the API reference, guides, and examples.

.. toctree::
   :maxdepth: 2
   :caption: Contents:

   getting_started
   user_guide
   api/index
   development/index
   changelog

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

docs/source/api/index.rst

API Reference
=============

This section documents the complete API of My App.

.. toctree::
   :maxdepth: 2

   models
   views
   controllers
   utils

Core Modules
------------

Models
~~~~~~

.. automodule:: myapp.models
   :members:
   :undoc-members:
   :show-inheritance:

Views
~~~~~

.. automodule:: myapp.views
   :members:
   :undoc-members:
   :show-inheritance:

Controllers
~~~~~~~~~~~

.. automodule:: myapp.controllers
   :members:
   :undoc-members:
   :show-inheritance:

docs/source/api/models.rst

Models
======

The models module contains all data models and business logic.

Data Model
----------

.. autoclass:: myapp.models.data_model.DataModel
   :members:
   :undoc-members:
   :show-inheritance:
   :special-members: __init__

Project Model
-------------

.. autoclass:: myapp.models.project_model.ProjectModel
   :members:
   :undoc-members:
   :show-inheritance:

Example Usage
-------------

Creating and using a data model::

    from myapp.models.data_model import DataModel

    # Create model
    model = DataModel()

    # Connect to signals
    model.data_changed.connect(lambda items: print(f"Items: {items}"))

    # Add items
    model.add_item("Task 1")
    model.add_item("Task 2")

    # Save to file
    model.save(Path("data.json"))

See Also
--------

* :class:`myapp.views.data_view.DataView` - View component
* :class:`myapp.controllers.data_controller.DataController` - Controller component

Makefile Targets

# docs/Makefile
.PHONY: help clean html livehtml

help:
    @echo "Please use \`make <target>' where <target> is one of"
    @echo "  html       to make standalone HTML files"
    @echo "  livehtml   to auto-rebuild on changes"
    @echo "  clean      to remove build files"

clean:
    rm -rf build/

html:
    sphinx-build -b html source build/html
    @echo "Build finished. The HTML pages are in build/html."

livehtml:
    sphinx-autobuild source build/html --open-browser

Auto-rebuild bei Änderungen:

pip install sphinx-autobuild

# Live preview
make livehtml


Read the Docs Integration

.readthedocs.yaml

# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html

version: 2

build:
  os: ubuntu-22.04
  tools:
    python: "3.11"

sphinx:
  configuration: docs/source/conf.py
  fail_on_warning: false

formats:
  - pdf
  - epub

python:
  install:
    - method: pip
      path: .
      extra_requirements:
        - docs

Requirements für RTD

docs/requirements.txt:

# Documentation dependencies for Read the Docs
sphinx>=7.0.0
sphinx-rtd-theme>=2.0.0
sphinx-autodoc-typehints>=1.24.0
myst-parser>=2.0.0

RTD Setup

  1. Account erstellen: https://readthedocs.org/
  2. Projekt importieren: Import a Project → GitHub
  3. Build starten: Automatic nach Git Push
  4. URL: https://myproject.readthedocs.io/

Versioning auf RTD

# .readthedocs.yaml
version: 2

sphinx:
  configuration: docs/source/conf.py

python:
  install:
    - method: pip
      path: .

# Build all versions
versions:
  latest:
    sphinx: 
      builder: html
  stable:
    sphinx:
      builder: html

Badge in README.md

# My Project

[![Documentation Status](https://readthedocs.org/projects/myproject/badge/?version=latest)](https://myproject.readthedocs.io/en/latest/?badge=latest)

Documentation: https://myproject.readthedocs.io/

PyCharm Integration

Docstring Format Einstellen

Settings → Tools → Python Integrated Tools → Docstring format - Wähle: Google

Quick Documentation

Keyboard Shortcuts: - Ctrl+Q (Windows/Linux) / F1 (macOS): Quick Documentation - Ctrl+P: Parameter Info - Ctrl+Shift+I: Quick Definition

Docstring Auto-Generation

def my_function(param1, param2):
    """
    # Drücke Enter nach """  PyCharm generiert:

    Args:
        param1: 
        param2: 

    Returns:

    """

Live Templates

Settings → Editor → Live Templates → Python

Neues Template erstellen: doc

"""
$DESCRIPTION$

Args:
    $ARGS$

Returns:
    $RETURNS$

Example:
    $EXAMPLE$
"""

External Tools für Sphinx

Settings → Tools → External Tools → Add

Name: Build Sphinx Docs Program: make Arguments: html Working Directory: $ProjectFileDir$/docs

Shortcut: Ctrl+Shift+D


Best Practices

Was dokumentieren?

✅ IMMER dokumentieren:

1. Public API (Classes, Functions, Methods)

class DataModel(QObject):
    """
    Core data model.  # ← Immer dokumentieren

    ...
    """

    def add_item(self, item: str) -> None:
        """Add an item."""  # ← Public method: Dokumentieren
        pass

2. Komplexe Algorithmen

def calculate_optimal_path(
    nodes: List[Node],
    weights: Dict[Tuple[Node, Node], float]
) -> List[Node]:
    """
    Calculate optimal path using Dijkstra's algorithm.  # ← Komplex: Erklären

    Implementation based on ...
    Time complexity: O(n²)
    Space complexity: O(n)
    """
    pass

3. Nicht-offensichtliches Verhalten

def save(self, path: Path) -> bool:
    """
    Save data to file.

    Warning:
        This method silently overwrites existing files without confirmation.
        # ← Wichtige Warnung dokumentieren
    """
    pass

4. Thread-Sicherheit

def process_data(self, data: bytes) -> None:
    """
    Process data in background.

    Note:
        This method is thread-safe and can be called from any thread.
        Results are emitted via the data_processed signal in the GUI thread.
        # ← Thread-Verhalten dokumentieren
    """
    pass

❌ NICHT dokumentieren:

1. Triviale Getter/Setter

@property
def name(self) -> str:
    # ❌ Nicht nötig zu dokumentieren
    return self._name

2. Offensichtliche Funktionen

def __str__(self) -> str:
    # ❌ Offensichtlich
    return self.name

3. Private Implementation Details

def _internal_helper(self):
    # ❌ Private, keine Doku nötig
    pass

Docstring Style Guide

Länge

# Kurz: Eine Zeile reicht
def clear(self) -> None:
    """Clear all items."""
    pass

# Mittel: Kurze Beschreibung + Details
def load(self, path: Path) -> bool:
    """
    Load data from file.

    Returns:
        True if successful, False otherwise.
    """
    pass

# Lang: Vollständige Dokumentation
def process_complex_data(
    self,
    data: Dict[str, Any],
    options: ProcessingOptions
) -> ProcessingResult:
    """
    Process complex data with specified options.

    This method performs multi-stage processing including validation,
    transformation, and aggregation. The process is optimized for
    large datasets and can run in a background thread.

    Args:
        data: Input data dictionary containing:
            - 'items': List of items to process
            - 'metadata': Processing metadata
        options: Processing options controlling:
            - Validation strictness
            - Transformation rules
            - Output format

    Returns:
        ProcessingResult containing:
            - status: 'success', 'partial', or 'failed'
            - processed_items: List of processed items
            - errors: List of errors if any occurred

    Raises:
        ValueError: If data structure is invalid
        ProcessingError: If processing fails

    Example:
        >>> data = {'items': [1, 2, 3], 'metadata': {}}
        >>> options = ProcessingOptions(strict=True)
        >>> result = processor.process_complex_data(data, options)
        >>> result.status
        'success'

    Note:
        This method is thread-safe and emits progress signals.
        For very large datasets (>1M items), consider using
        batch processing via :meth:`process_in_batches`.

    See Also:
        :meth:`process_in_batches`: Batch processing method
        :class:`ProcessingOptions`: Options documentation
    """
    pass

Type Hints + Docstrings

# ✅ Type Hints im Code, Beschreibung im Docstring
def calculate(price: float, tax_rate: float = 0.19) -> float:
    """
    Calculate price including tax.

    Args:
        price: Base price before tax
        tax_rate: Tax rate as decimal (0.19 = 19%)

    Returns:
        Total price including tax
    """
    return price * (1 + tax_rate)

# ❌ Typen wiederholen ist überflüssig
def calculate(price, tax_rate=0.19):
    """
    Calculate price including tax.

    Args:
        price (float): Base price before tax  # ← Überflüssig wenn Type Hints
        tax_rate (float): Tax rate            # ← Redundant

    Returns:
        float: Total price  # ← Überflüssig
    """
    return price * (1 + tax_rate)

Cross-Referencing

class DataModel(QObject):
    """
    Data model.

    See Also:
        :class:`DataView`: View component
        :class:`DataController`: Controller component
        :meth:`load`: Load data from file
        :attr:`items`: List of items
    """
    pass

def process(self, data: str) -> None:
    """
    Process data.

    Internally uses :func:`validate_data` and :func:`transform_data`.

    See Also:
        https://example.com/processing-guide
    """
    pass

Writing Examples

Use code blocks with :: for examples in docstrings. Do NOT use >>> prefixes - they trigger IDE syntax warnings and are meant for doctest-style executable examples.

Correct: Code Block Syntax

def complex_operation(a: int, b: int) -> int:
    """
    Perform complex operation.

    Example:
        Basic usage::

            result = complex_operation(5, 3)
            # result: 8

        With negative numbers::

            result = complex_operation(-5, 3)
            # result: -2
    """
    pass

Incorrect: Doctest Syntax (Avoid)

def complex_operation(a: int, b: int) -> int:
    """
    Example:
        >>> complex_operation(5, 3)   # Triggers IDE warnings
        8
    """
    pass

Multiple Examples

def load_data(path: Path) -> dict:
    """
    Load data from file.

    Example:
        Load from JSON file::

            data = load_data(Path("config.json"))
            print(data["setting"])

        Handle missing file::

            try:
                data = load_data(Path("missing.json"))
            except FileNotFoundError:
                data = {}
    """
    pass

Inline Results

For simple cases, show results as comments:

def add(a: int, b: int) -> int:
    """
    Add two numbers.

    Example::

        add(2, 3)  # Returns: 5
        add(-1, 1)  # Returns: 0
    """
    return a + b

Checklists

Pre-Commit Checklist

# 1. Docstrings vollständig?
ruff check --select D  # Check docstrings

# 2. Type Hints korrekt?
mypy src/

# 3. Docs bauen erfolgreich?
cd docs && make html

# 4. Keine Sphinx Warnings?
cd docs && make html 2>&1 | grep -i warning

Release Checklist

  • [ ] Alle public APIs dokumentiert
  • [ ] Type Hints überall
  • [ ] Examples getestet (doctest)
  • [ ] Sphinx build erfolgreich
  • [ ] RTD build erfolgreich
  • [ ] Changelog aktualisiert
  • [ ] Version Bump in __init__.py und conf.py

Tools & Commands

Lokale Dokumentation

# HTML Build
cd docs
make html
open build/html/index.html  # macOS
xdg-open build/html/index.html  # Linux
start build/html/index.html  # Windows

# Live Preview (auto-rebuild)
pip install sphinx-autobuild
make livehtml

# Clean & Rebuild
make clean && make html

# Check for broken links
make linkcheck

Docstring Coverage

# Install interrogate
pip install interrogate

# Check coverage
interrogate src/ -vv

# Generate badge
interrogate -g src/

Code Quality

# Type checking
mypy src/

# Docstring linting
ruff check --select D src/

# Full check
ruff check src/
mypy src/
interrogate src/

Example Project Structure

myproject/
├── src/
│   └── myapp/
│       ├── __init__.py              # """MyApp package."""
│       ├── models/
│       │   ├── __init__.py          # """Data models."""
│       │   └── data_model.py        # Full Google-style docs
│       ├── views/
│       │   ├── __init__.py
│       │   └── main_window.py       # Full docs with signals
│       └── controllers/
│           ├── __init__.py
│           └── app_controller.py    # Full docs with slots
├── docs/
│   ├── source/
│   │   ├── conf.py                  # Sphinx config
│   │   ├── index.rst
│   │   ├── api/
│   │   │   ├── index.rst
│   │   │   ├── models.rst
│   │   │   ├── views.rst
│   │   │   └── controllers.rst
│   │   ├── _static/
│   │   │   └── custom.css
│   │   └── _templates/
│   ├── build/
│   ├── Makefile
│   └── requirements.txt
├── tests/
├── .readthedocs.yaml
├── pyproject.toml
└── README.md

Quick Reference

Google Style Sections

"""
Brief description.

Detailed description (optional).

Args:
    param: Description

Returns:
    Description

Yields:
    Description (for generators)

Raises:
    ExceptionType: Description

Example:
    Code example

Note:
    Additional notes

Warning:
    Warning message

See Also:
    Related items

Todo:
    Future improvements

Attributes:
    attr: Description (for classes)
"""

Sphinx Directives

.. automodule:: myapp.models
   :members:
   :undoc-members:
   :show-inheritance:

.. autoclass:: DataModel
   :members:
   :special-members: __init__

.. autofunction:: process_data

.. note:: This is a note

.. warning:: This is a warning

.. code-block:: python

   code example

Resources

  • Google Style Guide: https://google.github.io/styleguide/pyguide.html
  • Sphinx Documentation: https://www.sphinx-doc.org/
  • Napoleon Extension: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
  • Read the Docs: https://docs.readthedocs.io/
  • Type Hints PEP: https://www.python.org/dev/peps/pep-0484/
  • Docstring PEP: https://www.python.org/dev/peps/pep-0257/

Happy Documenting! 📚