Good practices
This page is a non-exhaustive list of good practices in Python.
These good practices follow the PEP 8 style guide, which we advise you to read.
Naming conventions
In Python, the most common cases are: snake_case
(lower case separated by a _
), PascalCase
(capital letters as a separation, including the first word), and UPPER_CASE
. The usages are the following:
snake_case
for file names, folder names, variables, functions, methods, objects, and your package name.PascalCase
to define classes and exceptions.UPPER_CASE
for constants.
Note
For protected methods and internal functions, you can use a _
before, e.g. _my_function
. For private methods, use two _
, e.g. __my_method
.
Code readability
We listed below some advice to make the code easier to read.
- Use
_
to name a variable that will not be used: it avoids overloading your brain while reading the code. For instance, if we are only interested in the first value of a tuple:a, _ = f()
, or if you don't need the index in a for loop:for _ in range(10):
- Remove all your unused imports. It makes the code shorter, faster, and easier to read.
- Avoid having many nested loops. Instead, you can refactor your code to return early or make new functions. We recommend having at most two levels of nested indentation inside functions/methods.
Type hints
We recommend using type hints inside each function and method (see PEP guidelines). Two basic examples can be found below.
Note
When nothing is returned, None
can be used as the function output type. Also, some typings can be imported from typing
, e.g. List
.
from typing import List
def greeting(name: str) -> None:
print(f'Hello {name}!')
def list_multiplication(l: List[float], a: int) -> List[float]:
return [a * x for x in l]
There are many advantages of type hints. In particular, it:
- enables code completion
- is less error-prone (since your IDE may help you track bugs before they happen)
- improves readability
- guide the user to understand the input/output of any function
Catching wrong inputs
It's great to run a sanity check before running some functions to ensure every argument satisfies some constraints. When the arguments don't meet the constraints, you can return a meaningful error message, saving you a lot of bug-fixing time.
from model import MyModel
def f(x: MyModel, a: float):
assert isinstance(x, MyModel), f"The argument 'x' has to be a MyModel object, but found {type(x)}."
assert 0 < a < 1, f"The argument 'a' should be included in ]0, 1[, but found {a}."
...
Documentation
All packages must be documented. For that, a documentation is already ready-for-use (see its usage). In particular, every function and method should be properly described using docstrings.
Note
Comments can also be used but are not encouraged. Indeed, the usage of docstrings + type hints + meaningful variable names can be clearer than comments. Also, we may forget to update the comment, making the code inconsistent with the comments. Still, sometimes, it can be useful for some "tricky" parts of the code.
Code testing
Testing your code becomes crucial when your project gets bigger. Even though it takes some time to implement, it may save you twice this time in debugging. Mostly, it will improve your package's stability and ensure it's always fully functional. See here for an explanation of how to use pytest
for your project.
Code formatting
Tools like black help you format your code. It will make it more readable automatically. See here for an explanation of how to use black
for your project.
What not to do
- Never
import *
from a package. Otherwise, you never know which functions come from which package, which can cause conflicts/bugs. - Don't overwrite python builtins and reserved keywords, such as
None
,list
,id
, ... - Never use the
global
keyword, which is error-prone. - Never write code outside functions/classes. Instead, if you want a script to run code when executed, write it inside a
if __name__ == "__main__":
block. Else, the code would be executed if imported from other python files. - Never use a list (or any mutable variable) as a default argument of a function. Indeed, you'll not have the same default argument each time since it can be muted. For instance, if you set
default_arg=[]
, then it may not be empty at the next call. Instead, usedefault_arg=None
, and you can set it to[]
later in the code.