Forked and adapted from the Google style guide. Many references to Google are deliberately left in to minimize merge conflicts.
Where Drake-specific rules contradict previous Google conventions, the text is retained for reference but otherwise marked with a strikethrough. The style is intentionally difficult to read in the normal course of perusal. Copying-and-pasting, or even simply highlighting the text, will facilitate reading if necessary.
Rules specific to Drake are highlighted like this for clarity and easier maintainability.
A large number of sections are intentionally hidden, as they do not apply to Drake development. For all other aspects of code style, please see Drake's Code Style Guide documentation page.
Be sure to use the right style for module, function, method docstrings and inline comments.
In general, the semantic rules outlined below should be enforced by reviewers. However, the formatting (e.g. indentations, lines, etc.) will be enforced by automated lint tooling (e.g. `pycodestyle`), and should not be remarked upon by reviewers.Python uses docstrings to document code. A docstring is a string that is the
first statement in a package, module, class or function. These strings can be
extracted automatically through the __doc__
member of the object and are used
by pydoc
.
(Try running pydoc
on your module to see how it looks.) Always use the three
double-quote """
format for docstrings (per
PEP 257).
A docstring should be organized as a summary line (one physical line not
exceeding 80 characters) terminated by a period, question mark, or exclamation
point. When writing more (encouraged), this must be followed by a blank line,
followed by the rest of the docstring starting at the same cursor position as
the first quote of the first line.
Docstrings should conform to PEP 257 with one exception: summary lines are not
required (but are acceptable).
There are more formatting guidelines for
docstrings below.
This guide itself does not have comprehensive documentation on all directives available, but the Sphinx Napoleon documentation does: [`sphinx.ext.napoleon` docs](https://www.sphinx-doc.org/en/1.6.7/ext/napoleon.html) Every file should contain license boilerplate. Choose the appropriate boilerplate for the license used by the project (for example, Apache 2.0, BSD, LGPL, GPL) At present, Drake's code (C++, Python, Skylark, etc.) does not explicitly declare its license. Files should start with a docstring describing the contents and usage of the module.
"""A one line summary of the module or program, terminated by a period. Leave one blank line. The rest of this docstring should contain an overall description of the module or program. Optionally, it may also contain a brief description of exported classes and functions and/or usage examples. Typical usage example: foo = ClassFoo() bar = foo.FunctionBar() """
In this section, "function" means a method, function, or generator.
A function must have a docstring, unless it meets all of the following criteria:
- not externally visible
- very short
- obvious
A docstring should give enough information to write a call to the function without reading the function's code. The docstring should describe the function's calling syntax and its semantics, but generally not its implementation details, unless those details are relevant to how the function is to be used. For example, a function that mutates one of its arguments as a side effect should note that in its docstring. Otherwise, subtle but important details of a function's implementation that are not relevant to the caller are better expressed as comments alongside the code than within the function's docstring.
The docstring should be descriptive-style ("""Fetches rows from a Bigtable."""
) rather than imperative-style ("""Fetch rows from a Bigtable."""
). The docstring for a @property
data descriptor should use the
same style as the docstring for an attribute or a
function argument ("""The Bigtable path."""
,
rather than """Returns the Bigtable path."""
).
A method that overrides a method from a base class may have a simple docstring
sending the reader to its overridden method's docstring, such as """See base class."""
. The rationale is that there is no need to repeat in many places
documentation that is already present in the base method's docstring. However,
if the overriding method's behavior is substantially different from the
overridden method, or details need to be provided (e.g., documenting additional
side effects), a docstring with at least those differences is required on the
overriding method.
Certain aspects of a function should be documented in special sections, listed below. Each section begins with a heading line, which ends with a colon. All sections other than the heading should maintain a hanging indent of two or four spaces (be consistent within a file). These sections can be omitted in cases where the function's name and signature are informative enough that it can be aptly described using a one-line docstring.
Args:
: List each parameter by name. A description should follow the name, and be
separated by a colon followed by either a space or newline. If the
description is too long to fit on a single
80-character
79-character*
line, use a hanging
indent of 2 or 4 spaces more than the parameter name (be consistent with the
rest of the docstrings in the file). The description should include required
type(s) if the code does not contain a corresponding type annotation. If a
function accepts *foo
(variable length argument lists) and/or **bar
(arbitrary keyword arguments), they should be listed as *foo
and **bar
.
<span class="drake">Obvious parameters do not need to be documented.</span>
<span class="drake">\* See [PEP 8 - Maximum Line Length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length)</span>
Returns: (or Yields: for generators)
: Describe the type and semantics of the return value. If the function only
returns None, this section is not required. It may also be omitted if the
docstring starts with Returns or Yields (e.g. """Returns row from Bigtable as a tuple of strings."""
) and the opening sentence is sufficient to
describe the return value. Do not imitate 'NumPy style'
(example),
which frequently documents a tuple return value as if it were multiple
return values with individual names (never mentioning the tuple). Instead,
describe such a return value as: "Returns: A tuple (mat_a, mat_b), where
mat_a is ..., and ...". The auxiliary names in the docstring need not
necessarily correspond to any internal names used in the function body (as
those are not part of the API).
Raises: : List all exceptions that are relevant to the interface followed by a description. Use a similar exception name + colon + space or newline and hanging indent style as described in Args:. You should not document exceptions that get raised if the API specified in the docstring is violated (because this would paradoxically make behavior under violation of the API part of the API).
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
"""Fetches rows from a Smalltable.
Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle. String keys will be UTF-8 encoded.
Args:
table_handle: An open smalltable.Table instance.
keys: A sequence of strings representing the key of each table
row to fetch. String keys will be UTF-8 encoded.
require_all_keys: If True only rows with values set for all keys will be
returned.
Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
Returned keys are always bytes. If a key from the keys argument is
missing from the dictionary, then that row was not found in the
table (and require_all_keys must have been False).
Raises:
IOError: An error occurred accessing the smalltable.
"""
Similarly, this variation on Args:
with a line break is also allowed:
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
"""Fetches rows from a Smalltable.
Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle. String keys will be UTF-8 encoded.
Args:
table_handle:
An open smalltable.Table instance.
keys:
A sequence of strings representing the key of each table row to
fetch. String keys will be UTF-8 encoded.
require_all_keys:
If True only rows with values set for all keys will be returned.
Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
Returned keys are always bytes. If a key from the keys argument is
missing from the dictionary, then that row was not found in the
table (and require_all_keys must have been False).
Raises:
IOError: An error occurred accessing the smalltable.
"""
Classes should have a docstring below the class definition describing the class.
If your class has public attributes, they
should
may
be documented here in an
Attributes
section and follow the same formatting as a
function's Args
section.
Attributes are not always documented for classes.
class SampleClass:
"""Summary of class here.
Longer class information...
Longer class information...
Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""
def __init__(self, likes_spam: bool = False):
"""Inits SampleClass with blah."""
self.likes_spam = likes_spam
self.eggs = 0
def public_method(self):
"""Performs operation blah."""
All class docstrings should start with a one-line summary that describes what
the class instance represents. This implies that subclasses of Exception
should also describe what the exception represents, and not the context in which
it might occur. The class docstring should not repeat unnecessary information,
such as that the class is a class.
class CheeseShopAddress:
"""The address of a cheese shop.
...
"""
class OutOfCheeseError(Exception):
"""No more cheese is available."""
class CheeseShopAddress:
"""Class that describes the address of a cheese shop.
...
"""
class OutOfCheeseError(Exception):
"""Raised when no more cheese is available."""
Pay attention to punctuation, spelling, and grammar; it is easier to read well-written comments than badly written ones.
Comments should be as readable as narrative text, with proper capitalization and punctuation. In many cases, complete sentences are more readable than sentence fragments. Shorter comments, such as comments at the end of a line of code, can sometimes be less formal, but you should be consistent with your style.
Although it can be frustrating to have a code reviewer point out that you are using a comma when you should be using a semicolon, it is very important that source code maintain a high level of clarity and readability. Proper punctuation, spelling, and grammar help with that goal.
_ShortName = module_with_long_name.TypeWithLongName
ComplexMap = Mapping[str, list[tuple[int, int]]]
Other examples are complex nested types and multiple return variables from a function (as a tuple).
You can disable type checking on a line with the special comment # type: ignore
.
pytype
has a disable option for specific errors (similar to lint):
# pytype: disable=attribute-error
Annotated Assignments : If an internal variable has a type that is hard or impossible to infer, specify its type with an annotated assignment - use a colon and type between the variable name and value (the same as is done with function arguments that have a default value):
```python
a: Foo = SomeUndecoratedFunction()
```
Type Comments
: Though you may see them remaining in the codebase (they were necessary
before Python 3.6), do not add any more uses of a # type: <type name>
comment on the end of the line:
```python
a = SomeUndecoratedFunction() # type: Foo
```
Typed lists can only contain objects of a single type. Typed tuples can either have a single repeated type or a set number of elements with different types. The latter is commonly used as the return type from a function.
a: list[int] = [1, 2, 3]
b: tuple[int, ...] = (1, 2, 3)
c: tuple[int, str, float] = (1, "2", 3.5)
The Python type system has
generics. The factory
function TypeVar
is a common way to use them.
Example:
from typing import TypeVar
_T = TypeVar("_T")
...
def next(l: list[_T]) -> _T:
return l.pop()
A TypeVar can be constrained:
AddableType = TypeVar("AddableType", int, float, str)
def add(a: AddableType, b: AddableType) -> AddableType:
return a + b
A common predefined type variable in the typing
module is AnyStr
. Use it for
multiple annotations that can be bytes
or str
and must all be the same type.
from typing import AnyStr
def check_length(x: AnyStr) -> AnyStr:
if len(x) <= 42:
return x
raise ValueError()
A TypeVar must have a descriptive name, unless it meets all of the following criteria:
- not externally visible
- not constrained
Yes:
_T = TypeVar("_T")
AddableType = TypeVar("AddableType", int, float, str)
AnyFunction = TypeVar("AnyFunction", bound=Callable)
No:
T = TypeVar("T")
_T = TypeVar("_T", int, float, str)
_F = TypeVar("_F", bound=Callable)
Do not use
typing.Text
in new code. It's only for Python 2/3 compatibility.
Use str
for string/text data. For code that deals with binary data, use
bytes
.
def deals_with_text_data(x: str) -> str:
...
def deals_with_binary_data(x: bytes) -> bytes:
...
If all the string types of a function are always the same, for example if the return type is the same as the argument type in the code above, use AnyStr.
For symbols from the typing
and collections.abc
modules used to support
static analysis and type checking, always import the symbol itself. This keeps
common annotations more concise and matches typing practices used around the
world. You are explicitly allowed to import multiple specific classes on one
line from the typing
and collections.abc
modules. Ex:
from collections.abc import Mapping, Sequence
from typing import Any, Union
Given that this way of importing adds items to the local namespace, names in
typing
or collections.abc
should be treated similarly to keywords, and not
be defined in your Python code, typed or not. If there is a collision between a
type and an existing name in a module, import it using import x as y
.
from typing import Any as AnyType
Use conditional imports only in exceptional cases where the additional imports needed for type checking must be avoided at runtime. This pattern is discouraged; alternatives such as refactoring the code to allow top level imports should be preferred.
Imports that are needed only for type annotations can be placed within an if TYPE_CHECKING:
block.
- Conditionally imported types need to be referenced as strings, to be forward compatible with Python 3.6 where the annotation expressions are actually evaluated.
- Only entities that are used solely for typing should be defined here; this includes aliases. Otherwise it will be a runtime error, as the module will not be imported at runtime.
- The block should be right after all the normal imports.
- There should be no empty lines in the typing imports list.
- Sort this list as if it were a regular imports list.
import typing
if typing.TYPE_CHECKING:
import sketch
def f(x: "sketch.Sketch"): ...
Circular dependencies that are caused by typing are code smells. Such code is a good candidate for refactoring. Although technically it is possible to keep circular dependencies, various build systems will not let you do so because each module has to depend on the other.
Replace modules that create circular dependency imports with Any
. Set an
alias with a meaningful name, and use the real type name from
this module (any attribute of Any is Any). Alias definitions should be separated
from the last import by one line.
from typing import Any
some_mod = Any # some_mod.py imports this module.
...
def my_method(self, var: "some_mod.SomeType") -> None:
...
When annotating, prefer to specify type parameters for generic types; otherwise,
the generics' parameters will be assumed to be Any
.
def get_names(employee_ids: list[int]) -> dict[int, Any]:
...
# These are both interpreted as get_names(employee_ids: list[Any]) -> dict[Any, Any]
def get_names(employee_ids: list) -> Dict:
...
def get_names(employee_ids: List) -> Dict:
...
If the best type parameter for a generic is Any
, make it explicit, but
remember that in many cases TypeVar
might be more
appropriate:
def get_names(employee_ids: list[Any]) -> dict[Any, str]:
"""Returns a mapping from employee ID to employee name for given IDs."""
_T = TypeVar('_T')
def get_names(employee_ids: list[_T]) -> dict[_T, str]:
"""Returns a mapping from employee ID to employee name for given IDs."""
BE CONSISTENT.
If you're editing code, take a few minutes to look at the code around you and determine its style. If they use spaces around all their arithmetic operators, you should too. If their comments have little boxes of hash marks around them, make your comments have little boxes of hash marks around them too.
The point of having style guidelines is to have a common vocabulary of coding so people can concentrate on what you're saying rather than on how you're saying it. We present global style rules here so people know the vocabulary, but local style is also important. If code you add to a file looks drastically different from the existing code around it, it throws readers out of their rhythm when they go to read it. Avoid this.
-->