Linting your Python code

In this section, we will learn how to use linters, formatters, and pre-commit hooks to improve the quality of your code and automate the process of checking for errors and enforcing coding standards.

The Style Guide for Python Code is called PEP8 (or see this stylized presentation). Rules are proposed by the community of users, and subject to change. There are hundreds of rules as of 2024, and it would not be feasible (even desirable) to check them manually each time we write a code.

Linters: the flake8 example

A linter is a tool that analyzes your code for potential errors, bugs, and stylistic issues. It can help you catch mistakes early in the development process and ensure that your code is consistent and maintainable. There are many linters available for Python, but one of the most popular is flake8. flake8 is a command-line tool that runs several other linters, including pyflakes and pycodestyle, and reports any errors or warnings that it finds.

You can run flake8 from the command line to analyze your code. For example, to check all Python files in the current directory and its subdirectories, you can use the command flake8 . (see the documentation for more options/configuration).

flake8 will report any errors or warnings that it finds, along with the line number and a brief description of the issue. Fix the issues reported by flake8 and run it again to ensure that your code is free of errors and warnings.

Exercise: run flake8 on the module ~/robust-programming/tests/bad_quality_flake8.py and fix the errors:

flake8 ~/robust-programming/tests/bad_quality_flake8.py

Note that we installed flake8 but also some of its extensions to ensure a wider rule coverage:

flake8==7.0.0
flake8-docstrings==1.7.0
pep8-naming==0.13.3

Formatters: the black example

Complying to a coding style can be a tedious task, which a linter won’t help you with. A formatter can help you automate this process by automatically formatting your code to comply with a specific coding style.

One of the most popular formatters for Python is black. black reformats your entire file in place to comply with the black coding style (a strict subset of PEP 8). It is a great tool to enforce consistent formatting across your code base.

Exercise: first run black on the module ~/robust-programming/tests/bad_quality_black.py with the options --test and diff

black --check --diff ~/robust-programming/tests/bad_quality_black.py

--test runs black without overwriting the file, and --diff shows you what would have been changed: + are additions, and - are deletions. Once you understand better the changes, you can directly apply black to reformat the file:

black ~/robust-programming/tests/bad_quality_black.py

Linters vs formatters

Should you use a linter or a formatter? In practice both! They usually are very complementary, and your code will be only better by using both. Note however they can contredict each other sometimes, see e.g. the black FAQ to remedy to this problem.

When to run linters and formatters?

You can run your favourite linter and formatter on your local machine each time you make changes, but let’s face it: it will become annoying very quickly, and there is a high chance to forget to do it. Instead, you can automate the rule checks using pre-commit hooks and continuous integration jobs.

Pre-commit hooks

Pre-commit hooks are scripts installed in your .git/hooks directory that are run before each commit. They can be used to automate the process of checking for errors and enforcing coding standards, and can help you catch mistakes early in the development process.

In Python, the pre-commit package can be used to manage pre-commit hooks. It allows you to define a set of hooks in a configuration file, and then run them automatically before each commit.

Continuous integration

Pre-commit hooks are great, but they assume that you have your development environment installed in your local machine, which is not always the case. So instead of enforcing checks before committing the code, you can perform the check after each push in the repository. This is called continuous integration, and you will learn more at the end of this lecture (see continuous integration).