Python Style Guide
This section documents the specifics of the Python style preferred in the SweetPea code.
Note on PEP 8
In most cases, SweetPea’s code follows PEP 8, the official Python style guide. However, there are some extra rules we follow, and some rules we relax. These are all detailed below. If something is not detailed below, look for examples of similar constructs in the existing code. If nothing can be found to explain how something should be done, we defer to PEP 8 or, if PEP 8 has nothing to say on the matter, the developer’s intuition.
Maximum Line Length
Lines of code are limited to 120 characters in length. Comments and docstrings are limited to 80 characters in length.
Blank Lines
Top-level constructs, such as classes, functions, etc, are preceded by two blank lines.
Related single-line statements can be grouped or separated by single blank lines. For example, multiple type aliases can be separated into logical groups as follows:
T = TypeVar('T')
StrOrT = Union[str, T]
U = TypeVar('U')
Imports
Never use star-imports (i.e., imports of the form
from <module> import *
).Prefer explicit imports (i.e.,
from <module> import <item1>, <item2>
).Whole-module imports are allowed, but generally discouraged (i.e.,
import <module>
).Place imports after the module-level dunder names (e.g.,
__all__
).Note
This does not include
__future__
imports, which may only be preceded by the module docstring! An example is given below.Order imports as: standard library, third-party libraries, current library, with a blank line between each section.
Place whole-module imports within a section ahead of the explicit imports from the same section, separated by a blank line.
Prefer fully qualified imports instead of relative imports.
To put all that together:
"""This module does some neat things."""
from __future__ import annotations
__all__ = ['Export1', 'Export2']
import math
from dataclasses import dataclass
from typing import List
import numpy
from my_package.core_stuff import CoreThing
(Where my_package
is the module in which this code appears.)
String Quotes
Prefer double quotation marks "
for most strings. Single quotation marks
'
can be used for literal values, but we are moving away from this style.
Triple-quoted strings always use double quotation marks "
.
Trailing Commas
Prefer trailing commas in lists of items where each item appears on its own
line. For example, you may have a list of people where each Person
is
defined on its own line. Leave a trailing comma after the final element:
people = [
Person('Alex', 27),
Person('Taylor', 32),
Person('Cleopatra', 2091),
]
Type-Checking or Linting Overrides
Sometimes, the type-checkers or linters we use are wrong about something, possibly due to a bug or insufficiency. In these cases, they can be disabled on a line-by-line basis using specific inline comment forms.
To disable mypy checking, simply add # type: ignore
at the end of a line
that mypy is complaining about.
To disable pylint warnings, prefer to explicitly disable the warning raised on a
given name by doing pylint: disable=CHECK-TO-DISABLE
.
In all such cases, we strongly encourage placing a NOTE
-style block comment
on the preceding line explaining why the check was disabled. This should usually
point to a documented issue to support the choice.
For example, the two subclasses of Level
(SimpleLevel
and
DerivedLevel
) override the dataclasses
-implemented
__post_init__
methods with signatures that deviate from that of the base
Level
class. This is usually disallowed by the Liskov Substitution
Principle, which
says that subclasses should have compatible method signatures with their
parents. But this is a special case where such deviation is perfectly reasonable
and, in fact, necessary, so we had to disable type-checking and linting on those
lines. The SimpleLevel
definition looks like this, as of this writing
(with some unimportant parts abridged for clarity):
@dataclass(eq=False)
class SimpleLevel(Level):
"""... (Docstring removed for this example.)"""
weight: InitVar[int] = 1
# NOTE: The __post_init__ method is a special case where we can ignore the
# Liskov substitution property. This is addressed in
# python/mypy#9254:
# https://github.com/python/mypy/issues/9254
def __post_init__(self, weight: int): # type: ignore # pylint: disable=arguments-differ
super().__post_init__()
self._weight = weight
Note that this is also a case where comments may reach beyond the 80-character limit.
Tip
Combining the check-disabling directives can sometimes be confusing, but it
seems that the mypy # type: ignore
has to come first, and PyLint’s #
pylint: diable=CHECK-TO-DISABLE
has to come last.
Comments
Comments are part of the code. If you change some code in a way that renders the comment incorrect, your code is now incorrect. Update comments whenever you change the code the comment is documenting.
Comments should be written in complete sentences, with grammatical consistency, correct spelling, punctuation, etc.
Separate sentences with one space, not two.
Block Comments, Inline Comments, and Documentation Strings
Block comments are lines consisting of only comment material, written as one or more lines of (possibly indented) text written after a leading
#
on each line.Inline comments are comments placed on the same line as some code. We strongly discourage the use of inline comments, except where they are used for providing information to a static type-checker or a linter.
Documentation strings, generally called “docstrings”, are triple-quoted strings that adorn functions, classes, and modules. There are extra rules about docstrings, found in the reST style guide.
To put all these into an example: