Python Coding Guide¶
General Guide¶
We have non-software engineers and scientists working on the project so these guidelines are based on non-specialist python knowledge:
Work in your own ‘feature’ branch, merge into ‘dev’ - don’t push to main (it should be protected and yell at you)! We use the ‘dev’ branch as our main staging branch to ensure we are all in sync.
Follow the PEP8 style guide.
Use descriptive variable names, no single letter variables (double letters for iterators in numpy style are ok) single letter variables for indices / iterators are ok.
Use major function first variable names: e.g.
FieldScalar,FieldVectorandFieldTensorinstead ofScalarField,VectorFieldandTensorField.Type hint everything: e.g.
def add_ints(a: int, b: int) -> int:. This makes your code easier to understand and you have the possibility of compiling things if you need.pylintis a slow linter but will help you if you have type hinted everything.Ruffis another good option, it is faster but doesn’t pick up type hints as well.Use guard clauses (if statements) with returns at the top of functions to reduce the number of nested if/else structures.
Default mutable data types (lists, dicts, objects) to
Noneand then set them with an if statement guard clauseUse
pathliband thePathclass to manage all file io in preference to manual string handling.numpyandscipyare your friend - avoid for/while loops. Push everything you can down into C. Unless you are writing Cython then loops are great!Minimise dependencies as much as possible.
Avoid decorators unless absolutely necessary (
@dataclass,@abstractmethodand@staticmethodare examples that are ok)Don’t use
@propertyto hide complicated variable initialisation behind the.notation - in fact just avoid@propertyaltogether and just use a@dataclassfor data only classes.No inheritance unless it is an interface (python abstract base class
ABC) - use composition / dependency injection. See this video on the flaws of inheritance and this video on dependency injection.Only use one layer of abstraction - don’t inherit from multiple interfaces and don’t use mix-ins.
For interfaces (abstract base classes) prefix the name of the class with a capital
Ie.g.ISensorFor enumerations prefix the name with a capital
EsoEGeneratorType.Only use abstraction/interfaces when if/else or switch has at least 3 layers and/or becomes annoying.
Use a mixture of plain functions and classes with methods where and when they make sense.
Imports requiring many
.’s are annoying and the user finds the layers hard to remember. Bring everything to the top level so it can be accessed withpyvale.Setup good defaults for variables where possible so that the user can get started with minimal input.
Prefer dataclasses (
@dataclass) to dictionaries as they tell the user what parameters are needed and can have sensible defaults.When using dataclasses
def __post_init__():is useful for setting defaults for mutable data types.Use classes with
__slots__ = ("var1","var2",)as it is more memory efficient, faster and stops member variables being added dynamically. For dataclasses use:@dataclass(slots=True).Use code reviews to help each other and be nice / constructive as we are not all software engineers!
Documentation & Examples¶
Each new feature for pyvale requires documentation and user examples before being merged into the main package. For docstrings and documentation:
Every function should have accurate type hints
Use
numpystyle docstrings, there are plugins that can automate some of this if you have used type hints correctlyFor
numpyarrays make sure to include the meaning of each axis of the array and the expected shape of the array in the docstring. For example:
Examples should be placed in the ‘src/pyvale/examples/modulename’ (see here) directory where ‘modulename’ is the name of the module you have developed.
Testing¶
We use pytest as our main testing platform. Tests should be pragmatic and cover the following where applicable:
Specific algorithms (e.g. tensor rotations) and logic
Regression tests
Integration tests
End-to-end tests
Tests do not need to:
Have 100% code coverage
Test initialisation
If you find a bug or are fixing a bug please add a test for that bug as part of the fix.