commenting-c-and-python

star 2

Follow bocpy commenting and documentation conventions. Use when: adding comments to C or Python files, writing docstrings, documenting structs or classes, adding Doxygen doc-comments, using Sphinx param style, suppressing linter warnings with noqa, or following flake8 style rules (Q000, D205, D209, N802).

matajoh By matajoh schedule Updated 6/10/2026

name: commenting-c-and-python description: "Follow bocpy commenting and documentation conventions. Use when: adding comments to C or Python files, writing docstrings, documenting structs or classes, adding Doxygen doc-comments, using Sphinx param style, suppressing linter warnings with noqa, or following flake8 style rules (Q000, D205, D209, N802)."

Commenting C and Python Files

This skill describes the commenting conventions used across the bocpy project. Follow these patterns when adding or editing comments so the codebase stays consistent.

C Files (_core.c, _math.c)

Function-Level Documentation — Doxygen /// Style

Every non-trivial function gets a Doxygen doc-comment block immediately above its signature. Use triple-slash /// lines with @ tags in this order:

  1. @brief — one-line summary (sentence case, no trailing period)
  2. @details — optional longer explanation
  3. @note — optional caveats or side-effects
  4. @param — one per parameter (description starts uppercase)
  5. @return — what the function returns
/// @brief Creates a new BOCTag object from a Python Unicode string
/// @details The result object will not be dependent on the argument in any way
/// (i.e., it can be safely deallocated).
/// @param unicode A PyUnicode object
/// @param queue The queue to associate with this tag
/// @return a new BOCTag object
BOCTag *tag_from_PyUnicode(PyObject *unicode, BOCQueue *queue) {

For short helper functions, @brief alone is enough:

/// @brief Convenience method to obtain the interpreter ID
/// @return the ID of the currently running interpreter
static inline PY_INT64_T get_interpid() {

Struct Field Documentation

Document struct fields with /// @brief (and optionally /// @details) on the line(s) above the field:

typedef struct boc_message {
  /// @brief The tag associated with this message.
  /// @details This will be used by processes calling receive() and will create
  /// an affinity with a queue.
  struct boc_tag *tag;
  /// @brief whether the contents of this message were pickled
  bool pickled;
  /// @brief the threadsafe cross-interpreter data (the contents of the message)
  XIDATA_T *xidata;
} BOCMessage;

Inline Comments — // Style

An inline comment defaults to a single line of at most 120 characters, or it is deleted. Verbose multi-line inline comments rot as the code beneath them changes and make the code harder to read, not easier. Place the comment on the line above the code it describes, at the current indentation level:

  // swap the new node in as the new head
  node->next = head;

A multi-line // inline comment is permitted only when it records something the code cannot express and that does not fit on one line: a non-obvious concurrency invariant (2PL lock ordering, MCS handoff, memory-ordering rationale), the rationale above a version-gate #if/#elif ladder, an X-macro / clang-format off table boundary, or a reference anchor that needs a line of context. When in doubt, collapse to one line. (Doxygen /// / /** */ doc-blocks are documentation, not inline comments, and are exempt from this rule — they may carry in-depth prose.)

End-of-line // comments are reserved for preprocessor version annotations:

#if PY_VERSION_HEX >= 0x030E0000 // 3.14

/* */ Block Comments — Sentinel Only

Traditional block comments are used exclusively as sentinel markers in PyMethodDef and slot arrays:

    {NULL} /* Sentinel */

Do not use /* */ for any other purpose.

Exception: X-macro descriptor tables

Multi-line /* */ blocks immediately above an X(...)-style descriptor table (e.g. the BOC_AGG_OPS, BOC_BINARY_OPS, BOC_UNARY_OPS, BOC_2AGG_OPS tables in src/bocpy/_math.c) are permitted as an exception to the sentinel-only rule. The block must list: (1) the family's purpose, (2) how to add a new op, (3) the stamped symbol-naming scheme, and (4) the names in scope inside the per-op expression.

Method Table Doc Strings

Short one-phrase descriptions in PyMethodDef tables:

static PyMethodDef CownCapsule_methods[] = {
    {"get", CownCapsule_get, METH_NOARGS, "internal"},
    {"set", CownCapsule_set, METH_VARARGS, "internal"},
    {"bid", BehaviorCapsule_bid, METH_NOARGS, "Gets the ID for the behavior"},
};

Module Doc String

Set .m_doc in the PyModuleDef to a short description:

    .m_doc = "Provides the underlying C implementation for the core BOC "
             "functionality",

TODO Comments

Use // TODO on its own line, followed by the item(s):

// TODO
// invert 2x2, 3x3, general algorithm for NxN
// det
// trace

Python Files

Module-Level Docstring

Every .py file starts with a single-line triple-quoted docstring. Sentence case, ends with a period.

"""Behavior-oriented Concurrency."""
"""AST transformers that export when-decorated functions as behaviors."""

Class Docstrings

Use a triple-quoted docstring immediately after the class statement. For simple classes, a single-line docstring is preferred. For complex classes, use a summary line, a blank line, then a longer description. Sentence case, ends with a period.

class Cown(Generic[T]):
    """Lightweight wrapper around the underlying cown capsule."""
class MainModuleBinder(ast.NodeTransformer):
    """Prepares a main module for transpiling.

    This transformer collects the names of classes, functions, imports,
    and filters out everything else at the root level.
    """

Function / Method Docstrings

Start with a verb in imperative mood. Single-line for simple functions, multi-line for complex ones. Ends with a period.

Single-line:

def acquire(self):
    """Acquires the cown (required for reading and writing)."""

Multi-line (summary + body):

def release(self):
    """Release the cown to the next behavior.

    This is called when the associated behavior has completed, and thus can
    allow any waiting behavior to run.

    If there is no next behavior, then the cown's `last` pointer is set to null.
    """

Parameter Documentation — Sphinx Style

Use Sphinx :param: / :type: / :return: / :rtype: fields for parameter documentation. This is the single accepted style across the project.

def bind_module(tree: ast.Module, path: str = None) -> MainBindings:
    """Reduce a module to the bindings a worker needs.

    :param tree: The source tree
    :type tree: ast.Module
    :return: A bindings result with code and metadata
    :rtype: MainBindings
    """
def __init__(self, num_workers: Optional[int], export_dir: Optional[str]):
    """Creates a new Behaviors scheduler.

    :param num_workers: The number of worker interpreters to start.  If
        None, defaults to the number of available cores minus one.
    :type num_workers: Optional[int]
    :param export_dir: The directory to which the target module will be
        exported for worker import.  If None, a temporary directory will
        be created and removed on shutdown.
    :type export_dir: Optional[str]
    """

.pyi Stub File Docstrings

The stub file __init__.pyi uses Sphinx-style docstrings on every public function, class, and constant. Constants use an attribute docstring on the line after the declaration:

TIMEOUT: str
"""Sentinel value returned by :func:`receive` when a timeout occurs."""
def send(tag: str, contents: Any):
    """Sends a message.

    :param tag: The tag is an arbitrary label that can be used to receive this message.
    :type tag: str
    :param contents: The contents of the message.
    :type contents: Any
    """

Inline # Comments

An inline comment defaults to a single line of at most 120 characters, or it is deleted. Verbose multi-line inline comments rot as the surrounding code changes and reduce readability. Place the comment on the line above the code, at the current indentation:

        orphan_cowns = _core.cowns()
        if len(orphan_cowns) != 0:
            logger.debug("acquiring orphan cowns")
            # acquire orphaned cowns so their XIData is freed before teardown

A multi-line # inline comment is permitted only when it records something the code cannot express and that does not fit on one line: a non-obvious concurrency invariant, a behavior-changing transpiler rule, the rationale above a version gate, or a reference anchor that needs context. When in doubt, collapse to one line. Docstrings are not inline comments and are exempt — they should carry in-depth, useful documentation across as many lines as the reader needs.

Same-line # comments are acceptable for very short annotations:

        except RuntimeError:
            pass  # already destroyed

# noqa: Suppression Comments

Suppress linter warnings with # noqa: CODE at the end of the line. Place the suppression on the line that contains the violation, not a surrounding line:

    def visit_FunctionDef(self, node: ast.FunctionDef):  # noqa: N802
# CORRECT — noqa on the line that references the loop variable
@when(acc)
def _(a):
    a.value.add(val_to_add)  # noqa: B023

# WRONG — noqa on the def line does not suppress the reference on the next line
@when(acc)
def _(a):  # noqa: B023
    a.value.add(val_to_add)   # ← still triggers B023

# BEGIN / # END Markers

Code-generation insertion points in worker.py:

# BEGIN boc_export
# END boc_export

Linting Rules (flake8)

The project enforces style with flake8 (config in .flake8):

Rule Setting
inline-quotes double — use " not ' (Q000)
max-line-length 120
docstring-convention google (with Sphinx :param: fields — napoleon handles both)
extend-ignore E203, N812, N817
per-file-ignores test/*: D103 (missing public-function docstring), D403

Multi-line Class Docstrings (D205 / D209)

A multi-line docstring must have a summary line, a blank line, then the body. The closing """ must be on its own line:

# CORRECT
class Foo:
    """Short summary line.

    Longer description goes here after the blank line.
    """

# WRONG — triggers D205 and D209
class Foo:
    """This wraps onto a second line
    without a blank separator."""

Naming in Test Files (N802)

Test function names must be lowercase. Even when testing a property like .T, spell the test name with a lowercase letter:

# CORRECT
def test_t_equals_transpose(self, mat): ...

# WRONG — triggers N802
def test_T_equals_transpose(self, mat): ...

Quick Reference

Element C convention Python convention
Function docs /// @brief ... /// @return """Imperative summary."""
Struct/class docs /// @brief above each field """Single-line or multi-line.""" after class
Parameter docs /// @param name Description :param name: / :type name: (Sphinx)
Inline comments // on line above code # on line above code
End-of-line // for preprocessor version notes # for very short annotations or # noqa:
Block comments /* Sentinel */ only
Sentence case @brief starts with uppercase Docstrings start with uppercase
Trailing period No period on @brief Docstrings end with a period
TODOs // TODO on its own line # TODO on its own line
Docstring style Sphinx :param: / :type: / :return: / :rtype:

Common Pitfalls

Pitfall Fix
Using /* */ for documentation blocks in C Use /// Doxygen-style instead. /* */ is only for /* Sentinel */.
Using Google-style Args: blocks Use Sphinx :param: / :type: style instead. The project uses Sphinx autodoc for documentation.
Omitting the @brief tag in C doc-comments Always start with /// @brief. It is the minimum for every documented function.
Forgetting the trailing period in Python docstrings All Python docstrings end with a period.
Adding a trailing period to C @brief lines C @brief descriptions do not end with a period.
Duplicating comments Do not place a // block that repeats the /// doc-comment below it. Write it once.
Placing # noqa: on the wrong line Put it on the line with the actual violation, not a surrounding def or decorator line.
Multi-line class docstring without a blank line after the summary Add a blank line between the summary sentence and the body (D205). Close """ on its own line (D209).
Using single quotes in Python The project enforces double quotes (inline-quotes = double). Use "nan" not 'nan'.
Assigning Cown(m) to an unused variable If the return value is not needed (e.g., just releasing the matrix), call Cown(m) without assignment to avoid F841.
Install via CLI
npx skills add https://github.com/matajoh/bocpy --skill commenting-c-and-python
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator