Style Guide¶
Overview¶
We adhere to PEP8 with the exception of allowing a line length limit of 99 characters (as opposed to 79 characters). The standard limit of 72 characters for “flowing long blocks of text” in docstrings or comments is retained. We use Black as our PEP-8 Formatter and isort to sort imports. (Please set up a pre-commit hook to ensure any modified code passes format check before it is committed to the development repo.)
We aim to incorporate PEP 526 type annotations
throughout the library. See the Mypy type annotation cheat
sheet
for usage examples. Custom types are defined in typing
.
Our coding conventions are based on both the NumPy conventions and the Google docstring conventions.
Unicode variable names are allowed for internal usage (e.g. for Greek characters for mathematical symbols), but not as part of the public interface for functions or methods.
Naming¶
We follow the Google naming conventions:
Component |
Naming Convention |
---|---|
Modules |
module_name |
Package |
package_name |
Class |
ClassName |
Method |
method_name |
Function |
function_name |
Exception |
ExceptionName |
Variable |
var_name |
Parameter |
parameter_name |
Constant |
CONSTANT_NAME |
These names should be descriptive and unambiguous to avoid confusion within the code and other modules in the future.
Example:
d = 6 # Day of the week == Saturday
if d < 5:
print("Weekday")
Here the code could be hard to follow since the name d
is not
descriptive and requires extra comments to explain the code, which
would have been solved otherwise by good naming conventions.
Example:
fldln = 5 # field length
This could be improved by using the descriptive variable field_len
.
Things to avoid:
- Single character names except for the following special cases:
counters or iterators (
i
,j
);e as an exception identifier (
Exception e
);f as a file in
with
statements;mathematical notation in which a reference to the paper or algorithm with said notation is preferred if not clear from the intended purpose.
- Trailing underscores unless the component is meant to be protected or private:
protected: Use a single underscore,
_
, for protected access; andpseudo-private: Use double underscores,
__
, for pseudo-private access via name mangling.
Displaying and Printing Strings¶
We follow the Google string conventions. Notably, prefer to use Python f-strings, rather than .format or % syntax. For example:
state = "active"
print("The state is %s" % state) # Not preferred
print(f"The state is {state}") # Preferred
Imports¶
We follow the Google import conventions. The
use of import
statements should be reserved for packages and
modules only, i.e. individual classes and functions should not be
imported. The only exception to this is the typing module.
Use
import x
for importing packages and modules, where x is the package or module name.Use
from x import y
where x is the package name and y is the module name.Use
from x import y as z
if two modules namedy
are imported or ify
is too long of a name.Use
import y as z
whenz
is a standard abbreviation likeimport numpy as np
.
Variables¶
We follow the Google variable typing conventions which states that there are a few extra documentation and coding practices that can be applied to variables such as:
One may type a variables by using a
: type
before the function value is assigned, e.g.,a: Foo = SomeDecoratedFunction()
Avoid global variables.
A function can refer to variables defined in enclosing functions but cannot assign to them.
Parameters¶
There are three important style components for parameters inspired by the NumPy parameter conventions:
Typing
We use type annotations meaning we specify the types of the inputs and outputs of any method. From the
typing
module we can use more types such asOptional
,Union
, andAny
. For example,def foo(a: str) -> str: """Takes an input of type string and returns a value of type string""" ...
Default Values
Parameters should include
parameter_name = value
where value is the default for that particular parameter. If the parameter has a type then the format isparameter_name: Type = value
. When documenting parameters, if a parameter can only assume one of a fixed set of values, those values can be listed in braces, with the default appearing first. For example,""" letters: {'A', 'B, 'C'} Description of `letters`. """
NoneType
In Python,
NoneType
is a first-class type, meaning the type itself can be passed into and returned from functions.None
is the most commonly used alias forNoneType
. If any of the parameters of a function can beNone
then it has to be declared.Optional[T]
is preferred overUnion[T, None]
. For example,def foo(a: Optional[str], b: Optional[Union[str, int]]) -> str: ...
For documentation purposes,
NoneType
orNone
should be written with double backticks.
Docstrings¶
Docstrings are a way to document code within Python and it is the first statement within a package, module, class, or function. To generate a document with all the documentation for the code use pydoc.
Typing¶
We follow the NumPy parameter conventions. The following are docstring-specific usages:
Always enclose variables in single backticks.
For the parameter types, be as precise as possible, do not use backticks.
Modules¶
We follow the Google module conventions. Notably, files must start with a docstring that describes the functionality of the module. For example,
"""A one-line summary of the module must be terminated by a period.
Leave a blank line and describe the module or program. Optionally
describe exported classes, functions, and/or usage examples.
Usage Example:
foo = ClassFoo()
bar = foo.FunctionBar()
""""
Functions¶
The word function encompasses functions, methods, or generators in this section. The docstring should give enough information to make calls to the function without needing to read the functions code.
We follow the Google function conventions. Notably, functions should contain docstrings unless: - not externally visible (the function name is prefaced with an underscore) or - very short.
The docstring should be imperative-style """Fetch rows from a
Table"""
instead of the descriptive-style """Fetches rows from a
Table"""
. If the method overrides a method from a base class then it
may use a simple docstring referencing that base class such as
"""See base class"""
, unless the behavior is different from the
overridden method or there are extra details that need to be
documented.
- Args:
List each parameter by name, and include a description for each parameter.
- Returns: (or Yield in the case of generators)
Describe the type of the return value. If a function only returns
None
then this section is not required.
- Raises:
List all exceptions followed by a description. The name and description should be separated by a colon followed by a space.
Example:
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Fetch rows from a Smalltable.
Retrieve 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: Optional
If `require_all_keys` is ``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¶
We follow the Google class conventions. Classes, like functions, should have a docstring below the definition describing the class and the class functionality. If the class contains public attributes, the class should have an attributes section where each attribute is listed by name and followed by a description, separated by a colon, like for function parameters. For example,
class foo:
"""One-liner describing the class.
Additional information or description for the class.
Can be multi-line
Attributes:
attr1: First attribute of the class.
attr2: Second attribute of the class.
"""
def __init__(self):
"""Should have a docstring of type function."""
pass
def method(self):
"""Should have a docstring of type: function."""
pass
Extra Sections¶
We follow the NumPy style guide. Notably, the following are sections that can be added to functions, modules, classes, or method definitions.
See Also:
Refers to related code. Used to direct users to other modules, functions, or classes that they may not be aware of.
When referring to functions in the same sub-module, no prefix is needed. Example: For
numpy.mean
inside the same sub-module:""" See Also -------- average: Weighted average. """
For a reference to
fft
in another module:""" See Also -------- fft.fft2: 2-D fast discrete Fourier transform. """
Notes
Provide additional information about the code. May include mathematical equations in LaTeX format. For example,
""" Notes ----- The FFT is a fast implementation of the discrete Fourier transform: .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} """
Math can also be used inline:
""" Notes ----- The value of :math:`\omega` is larger than 5. """
For a list of available LaTex macros, search for “macros” in docs/source/conf.py.
Examples:
Uses the doctest format and is meant to showcase usage.
- If there are multiple examples include blank lines before and
after each example. For example,
""" Examples -------- Necessary imports >>> import numpy as np Comment explaining example 1. >>> np.add(1, 2) 3 Comment explaining a new example. >>> np.add([1, 2], [3, 4]) array([4, 6]) If the example is too long then each line after the first start it with a ``...`` >>> np.add([[1, 2], [3, 4]], ... [[5, 6], [7, 8]]) array([[ 6, 8], [10, 12]]) """
Markup¶
The following components require the recommended markup taken from the NumPy Conventions.:
Paragraphs: Indentation is significant and indicates the indentation of the output. New paragraphs are marked with a blank line.
Variable, parameter, module, function, method, and class names: Should be written between single back-ticks (e.g. `x`, rendered as x), but note that use of Sphinx cross-reference syntax is preferred for modules (:mod:`module-name` ), functions (:func:`function-name` ), methods (:meth:`method-name` ) and classes (:class:`class-name` ).
None, NoneType, True, and False: Should be written between double back-ticks (e.g. ``None``, ``True``, rendered as
None
,True
).Types: Should be written between double back-ticks (e.g. ``int``, rendered as
int
). NumPy dtypes, however, should be written using cross-reference syntax, e.g. :attr:`~numpy.float32` forfloat32
.
Other components can use *italics*, **bold**, and ``monospace``
(respectively rendered as italics, bold, and monospace
) if needed, but
not for variable names, doctest code, or multi-line code.
Documentation¶
Documentation that is separate from code (like this page) should follow the IEEE Style Manual. For additional grammar and usage guidance, refer to The Chicago Manual of Style. A few notable guidelines:
Equations which conclude a sentence should end with a period, e.g., “Poisson’s equation is
\[\Delta \varphi = f \;."\]Do not capitalize acronyms or inititalisms when defining them, e.g., “computer-aided system engineering (CASE),” “fast Fourier transform (FFT).”
Avoid capitalization in text except where absolutely necessary, e.g., “Newton’s first law.”
Use a single space after the period at the end of a sentence.
The source code (.rst files) for these pages does not have a hard line-length guideline, but line breaks at or before 79 characters are encouraged.
Comments¶
There are two types of comments: block and inline. A good rule of thumb to follow for when to include a comment in your code is if you have to explain it or is too hard to figure out at first glance, then comment it. An example of this, taken from the Google comment conventions, is complicated operations which most likely require a block of comments beforehand.
If a comment consists of one or more full sentences (as is typically the case for block comments), it should start with an upper case letter and end with a period. Inline comments often consist of a brief phrase which is not a full sentence, in which case they should have a lower case initial letter and not have a terminating period.