Some tools are absolute game-changers for coding quality and development productivity. They not only help you avoid bugs but also enhance code readability, making your team members happier. Having used several of these tools over the years, I’m excited to share my favorite picks for improving code quality.
All the code used in this example is stored in this repo. Let’s dive in!
Code formatters (e.g. Black)
CF (Code Formatter) is a powerful tool that automatically reformats your code to adhere to a predefined standard, typically following the guidelines of PEP-8, the official Python style guide. It is a paradigm shifting approch that simplifies your coding process, allowing you to focus solely on writing useful code without worrying about formatting, spacing, and other minor details. With CF, your code becomes a thing of beauty, making your development life much more enjoyable and productive.
Using Black
# ./lib/demo_black.py
# Stretched print statement
print(
"Hi"
)
# Over-spaced loop
for i in [1,2,3]:
print(f"Iteration={i}")
#Not-spaced comment
black ./lib/demo_black.py
>>> reformatted lib/demo_black.py
>>> All done! ✨ 🍰 ✨
>>> 1 file reformatted.
# Stretched print statement
print("Hi")
# Over-spaced loop
for i in [1, 2, 3]:
print(f"Iteration={i}")
# Not-spaced comment
Alternatives to Black: autopep8.
Code linters (e.g. Pylint)
A linter is a term used to describe a category of tools that perform static code analysis, examining source code without executing it, to identify potential issues. Specifically, a code linter is a valuable tool extensively used in software development to analyze source code for potential errors, style inconsistencies, and adherence to coding standards. By scanning the code, it offers feedback on areas that may benefit from improvement or correction.
Using Pylint
# ./lib/demo_pylint.py
x = 1
y = 2
print(x + y)
pylint ./lib/demo_pylint.py
>>> ************* Module lib.demo_pylint
>>> lib/demo_pylint.py:1:0: C0114: Missing module docstring (missing-module-docstring)
>>> lib/demo_pylint.py:2:0: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
lib/demo_pylint.py:3:0: C0103: Constant name "y" doesn't conform to UPPER_CASE naming style (invalid-name)
>>> -----------------------------------
>>> Your code has been rated at 0.00/10
Alternatives to Pylint: PyFlakes, Flake8.
Type checkers (e.g. MyPy)
A code type checker is a tool used in languages with static typing, like Python with type hints (introduced in PEP 484). It analyzes the code to ensure variables, functions, and expressions use the correct data types specified by annotations. Type hints are not enforced at runtime but help type checkers during static analysis.
Using MyPy
# ./lib/demo_mypy.py
def print_sum(x: int, y: int) -> int: # Should return None
print(x + y)
def get_product(x: int, y: int) -> str: # Should return int
return x + y
a = "a"
b = "b"
print(a * b) # Invalid format
mypy ./lib/demo_mypy.py
>>> lib/demo_mypy.py:2: error: Missing return statement [return]
>>> lib/demo_mypy.py:7: error: Incompatible return value type (got "int", expected "str") [return-value]
>>> lib/demo_mypy.py:12: error: Unsupported operand types for * ("str" and "str") [operator]
Found 3 errors in 1 file (checked 1 source file)
Alternatives: to MyPy Pyright, Pyre, Ptype.
Import formatters (e.g. Isort)
Isort is a Python utility that automatically organizes import statements according to coding conventions. It groups imports into standard library, third-party libraries, and local project modules for a clean order. Isort handles multiline imports, sorting them alphabetically. By using Isort, developers maintain a standardized import style, avoid manual sorting, and reduce code review discussions.
Using Isort
import sys # Initially 1
import json # Initially 2
import os # Initially 3
isort ./lib/demo_isort.py
>>> Fixing /Users/myuser/code/tools-for-every-python-dev/lib/demo_isort.py
import json # Initially 2
import os # Initially 3
import sys # Initially 1
Alternatives: N/A.
Enforcing formatting on commit (Pre-commit hook)
Last but not least, let’s address the question of how we ensure that all the implemented checks and rules are followed by everyone. The answer lies in using pre-commit hooks. By setting up pre-commit hooks, each time we commit our code via the command line or code editor, all desired checks are automatically executed. Only when all checks pass successfully, the code can be committed. This ensures consistent code quality and adherence to the defined rules for every team member.
Example pre-commit file
# ./.pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
language_version: python3.10
- repo: https://github.com/pycqa/pylint
rev: pylint-2.8.0
hooks:
- id: pylint
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.0
hooks:
- id: mypy
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
Final words
In conclusion, I cannot overemphasize the significance of utilizing the aforementioned tools for mature software teams. While there are many other essential elements of great code, the tools described above can take us a long way with relatively low setup costs. Embracing these tools can greatly enhance code quality, maintainability, and team productivity, making them a valuable asset for any development project. Wishing you success in all your future projects!
Cheers,
Filip