Python Conventions
Coding Conventions
A summary of a conventions used throughout the various OGD Python codebases:
None of these are really mandatory in any sense, but they do help keep things consistent and organized.
Software Platform
We use the software packages and versions listed in our Open Game Data Reference Platform document
Casing
We generally use different casing conventions for different parts of the code:
PascalCase: Use for classes, properties, and public functionscamelCase: Use for “protected” and “private” functions. Prefix private functions with an underscore:_privateFunctionsnake_case: Use for variables, including instance variables (e.g.self._var). Prefix private and temp variables with an underscore:self._private_varor_temp_varNote: A “temp” variable here refers to one that is used only to hold a value for use in other calculations, and is not operated upon or updated/modified directly. For example, consider the following code:
lst = [] for i in range(10): _x = round(i + 3 * 3 / (i - 6)) _y = complicated.function(foo=2*i).chain(bar=i+1, baz=_x) lst.append(_y)
Here,
lstis a variable that is repeatedly operated on, so it is a “normal” local var._xand_yexist only to hold values temporarily to simplify the code using those values. Thus,_xand_yare considered “temp” variables and have the_prefix.
Alignment
Quote Characters
When defining a string variable, use double-quotes:
"...".When using a string index into a dictionary, use single-quotes:
'...'
Type Hinting
Python does not have formal type-checking, but provides type “hints” to help a linter pre-check for potential type errors. In general, we do our best to add type hints wherever practical and cut down on exceptions.
The three primary cases for type hints are:
Variables: When a variable is first used/bound/whatever, we attach a type hint between the variable name and the assignment operator. For example:
some_int_var : int = 5We leave a space before and after the colon. For instance variables that are not initialized with a simple assignment (e.g.self.var : str = "foo"), simply “declare” the variable, optionally giving it a temporary variable (such as an empty list, for aListvariable) and initialize later. This allows us to keep__init__functions relatively well-organized, with a section of one-line declarations/initializations followed by a section of complicated initializations. For example:self._simple_var : int = 0 self._complicated_var : Dict[str, List[int]] = {} self._another_var : str = "Some string" for i in range(20): self._complicated_var[f'Item{i}'] = [x*i for x in range(3)]
Function parameters: Function parameters are type-hinted with a similar syntax to variables. For function params STUB: This section is on the to-do list.
Function return values: STUB: This section is on the to-do list.
Organization of Imports in a Module
Generally, the imports in a file are organized into sections, with each section marked by a comment.
The sections go in the following order:
Standard Libraries: Imports of built-in, standard library functions. For example, in Python, this might include
datetime,pathlib, andtyping.3rd-Party Libraries: These are imports from libraries outside the language/framework standard libraries. For example, in Python, this might include
pandas,numpy, orpyplot.OGD Imports: Imports from OpenGameData libraries outside the local project.
Local Imports: Imports from the local project.
For easy copy-paste of section comments:
# import standard libraries
# import 3rd-party libraries
# import OGD libraries
# import local files
Organization of Functions in a Class
Generally, the functions of a class are organized into sections, with each section marked by a comment.
The sections go in the following order:
Define Abstract Functions: This only applies to abstract base classes. Typicallly abstracts will be private functions, called from a public method that handles cross-cutting concerns (e.g. logging)
Built-ins & Properties: At minimum, a constructor; typically includes a
to_string-style function (e.g.__str__and/or__repr__in Python) and any operator overloads. Properties are getter and setter functions for languages that support them. For OGD, that’s basically just Python.Implement Abstract Functions: Public abstracts should come first (if any, see note for Define Abstract Functions), followed by private abstracts.
Public Statics: No notes
Public Methods: No notes
Private Statics: No notes
Private Methods: Generally reserved for helper functions for public methods.
Sections 1 and 3 are optional, in the sense that no comment is needed to mark these sections if the given class is not an abstract base class and/or does not inherit from one, and thus does not define and/or implement any abstract functions. All other sections are required, even if the given class does not implement any functions for a given section.
For easy copy-paste of section comment headers:
# *** ABSTRACTS ***
# *** BUILT-INS & PROPERTIES ***
# *** IMPLEMENT ABSTRACT FUNCTIONS ***
# *** PUBLIC STATICS ***
# *** PUBLIC METHODS ***
# *** PRIVATE STATICS ***
# *** PRIVATE METHODS ***
Chapter 3: Python Tips & Tricks
A list of various little niceties in Python that you can use when writing your Python code:
Index dictionaries with
d.get('key', default_val)instead ofd['key']Loop lists of objects with
enumerate(container_ob)to get an index with each objectConvert
forloops with list appends into list comprehensions
Comment Blocks
Each module, class, and function should have a comment block. The blocks should use the Sphinx style; this style can be configured in the autoDocstring extension for VS Code. If the block description is longer than one line, the first line should be a simple summary, with a detailed description given in a paragraph, separated from the summary line by a single blank line. The detailed paragraph should use Markdown formatting, following all conventions for writing Markdown
Parameters and return values should be specified after the body, in the case of function comment blocks. The parameter list should be separated from the body by a single blank line.
To-do items should be specified after the parameters, using the Sphinx directive
.. todo::. The to-do items should be separated from the parameters by a single blank line.An example is given below:
Class Comments
For all abstract classes, the comment block should include a line listing all abstract functions to be implemented. For example:
Function Comments