First of all, let's talk about formatting. It may sound like a minor issue—and it generally is—but formatting won't affect your code performance. In a team, however, formatting matters. It improves readability and allows quicker reading through the code; good formatting highlights both typical and non-trivial areas of the code, helping to skim through trivial parts and focus on what's important. At the same time, formatting, if not automated, takes time and can cause arguments within a team, given that PEP8 does not have strict rules on any single aspect, and there are always matters of taste.
Now, there are quite a few tools that help with formatting—and statically finding potential issues in code—including wrong syntax, non-used variables, and so on. These tools are called linters. Arguably the most popular linter for Python is flake8. Under the hood, it combines three linters:
- PyFlake 8
- pycodestyle (formerly PEP8)
- McCabe
Another popular one is pylama, which combines seven linters, including the preceding ones, under the hood (it helps with linting docstrings, too!). Among others, there is Bandit, Radon, and MyPy, which specifically check code versus the given type hints. The good news is that many IDEs and code editors support running linters in the background, highlighting potential errors while you code. In order to use one in VS Code, just go to the command palette and type select linter—VS Code will offer you a list of supported ones and will install and start running the chosen one all by itself.
You should definitely use linters! However, they were designed to inform you, and can be configurable (for example, to ignore specific errors). To automate the process further—and make everyone on the team follow the same set of formatting rules—we will introduce black.
black is designed to be a deterministic, automated formatter. It is easy to set up as a pre-commit hook (in other words, it will run automatically before every Git commit). Therefore, you don't need to change your personal formatting habits (or lack thereof)—once the code is ready to be committed, black will take over and process everything. The best part is that black is not configurable, so there is no room for debates in the team regarding which formatting style is the best.
Let's check whether we can improve the readability of our wikiwwii package. black has a diff option and will show which files will be changed without changing them. Let's run this first:
- In the repository root folder, type the following in the Terminal:
black ./wikiwwii --diff
Quite a few lines were affected—black replaces all the single quotation marks with doubles, makes sure that the comment symbol is separated from the code by two whitespaces, and so on and so forth. Where possible, it keeps elements on the same line—if not, it will keep every argument on the same indentation level.
- Let's run that without --diff to reformat our code. Feel free to revise all the changes via VS Code:
The preceding is a diff visualization (available via the GIT tab) of the file before and after black formatting (on the left, red lines and characters with a minus sign near the line number were removed/modified, while green ones with the plus sign on the right were added or changed).
I think you'll agree that those changes make sense—some of them are more important than others, but still, it definitely looks better than it did before.
- Now, how could we set that to run automatically? The easiest way is to leverage another package that deals with GitHub hooks, called pre-commit. In order to use it, we'll create a new file in the repository's root and name it .pre-commit-config.yaml. Inside, type the following settings:
repos: - repo: https://github.com/python/black rev: stable hooks: - id: black language_version: python3.7
With that setting in place, we can run pre-commit install, which will "deploy" the preceding settings into a hook.
- Finally, we can set a few settings that black accepts. As per the developers' recommendation, it is better to set that up via the pyproject.toml file:
[tool.black]
line-length = 88
target-version = ['py37', 'py38']
exclude = '''
/(
.eggs
| .git
| .hg
| .mypy_cache
| .tox
| .venv
| .dvc
| _build
| buck-out
| build
| dist
)/
'''
Now, everything should be in place. Let's try committing the changes.
For the first run, the black hook will take a few seconds to download and run. From now on, if the code is not formatted on a Git commit, it will be reformatted, and the commit process will halt (so that you can check the commit results). Once you feel safe to proceed, commit one more time, and you're all good. The best part is that once this code is on GitHub, every collaborator will have to format with those exact settings!
Lastly, we want to add black to our development dependencies in the pyproject.toml file so that our fellow developers get black as part of their development environment automatically:
[tool.poetry.dev-dependencies]
pytest = "^3.0"
pytest-cov = "^2.7"
pytest-azurepipelines = "^0.6.0"
black = "^19.3"
Don’t forget to run poetry add black and poetry update. For more on black (or, rather, the motivation behind it), please check out this video from PyCon 2019 by Łukasz Langa, the creator of black: https://www.youtube.com/watch?v=ia19n_yK4Qs.
Good code formatting is important, and settling on one style within the team is even more so. But what are the other dimensions of good code? And, more importantly, how can we measure them? That's what we'll talk about in the next section.