First things first
Hello everyone, I’m Thanh Tung, a developer with N years of experience.
When working in teams, each person has their own coding style, which makes code reading and maintenance difficult, leading to increased development time and reduced project efficiency.
This motivated me to write this article to share my experience using tools to improve code quality, specifically for Python.
Here are some reasons to standardize code:
- Code becomes more readable and maintainable
- Reduces bugs and potential errors
- Improves code review efficiency
- Ensures everyone in the team follows the same standards
- And so on and so forth
If you’re looking for ways to improve your Python code quality, this article is for you.
Let’s get started!
Main tools I use
- Black: code formatter
- Flake8: linter
- Pylint: linter
- Commitlint: commit message linter
1. Black - The Uncompromising Code Formatter
Black is an automatic code formatter for Python. What’s special about Black is that it’s “uncompromising” - meaning you don’t need much configuration, Black will format code according to a specific standard.
Installing Black:
pip install black
Using Black:
Format a single file
black your_file.py
Format all files in a directory
black .
Example
Before using Black:
def foo(
):
print ("Hello world!")
print ("Hello world!")
print (
"Hello world!"
)
After using Black:
def foo():
print("Hello world!")
print("Hello world!")
print("Hello world!")
Looks better now, doesn’t it?
2. Flake8 - Style Guide Enforcement
Flake8 is a popular linting tool that combines features from PyFlakes, pycodestyle, and Ned Batchelder’s McCabe script.
Installing Flake8:
pip install flake8
Using Flake8:
flake8 your_file.py
Example
Invalid code:
def my_function():
# This is a really long comment that exceeds the 79-character limit and causes a line length error in Flake8.
return 42
Flake8 will report:
examples/use_flake8.py:3:80: E501 line too long (113 > 79 characters)
Valid code:
def my_function():
# This is a really long comment that exceeds the 79-character limit
# and causes a line length error in Flake8.
return 42
3. Pylint - A Tool for Style Checks
Pylint is a Python code analysis and evaluation tool. It checks code against various standards, from line length, nesting depth, to the usage of variables, functions, classes, and more.
Installing Pylint:
pip install pylint
Using Pylint:
pylint your_file.py
Example
Invalid code:
def my_function():
return 42
Pylint will report:
# ************* Module try_pylint
# your_file.py:2:0: C0304: Final newline missing (missing-final-newline)
# your_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)
# -----------------------------------
# Your code has been rated at 0.00/10
Valid code:
"""This is a docstring for the module."""
def my_function():
"""This is a docstring for the function."""
return 42
You might wonder what’s the difference between Flake8 and Pylint since they’re both linters?
The answer is yes, there are differences:
- Flake8 is a simpler linter, checking only basic issues like line length, formatting, etc.
- Pylint is more complex, checking more issues including line length, formatting, etc.
Specifically: see more
Problem | Pylint | Flake8 |
---|---|---|
Unnecessary semicolon | ✅ | ✅ |
Missing newlines | ✅ | ✅ |
Missing whitespaces | ❌ | ✅ |
Missing docstring | ✅ | ❌ |
Unused arguments | ✅ | ✅ |
Incorrect naming style | ✅ | ❌ |
Selecting element from the list with incorrect index | ❌ | ❌ |
Dividing by zero expression | ❌ | ❌ |
Missing return statement | ✅ | ❌ |
Calling method that doesn’t exist | ✅ | ✅ |
Unnecessary pass statement | ✅ | ❌ |
4. Commitlint - Linting Git Commit Messages
Commitlint is a commit message linting tool that helps ensure commit messages follow a specific standard.
This reminds me of my colleagues who often write very messy commit messages :))
Or like this:
Of course, writing like this makes it incomprehensible to anyone, including the author (after a few days). To solve this issue, I started using Commitlint.
Commitlint rules (default):
Commit message header should be between 10 and 50 characters
- => No more
test
,fix bug
orupdate ui
commits ^^
- => No more
Commit message body should be between 0 and 72 characters
Commit message footer should be between 0 and 72 characters
Commit message structure following Conventional Commits:
- Must start with a type (feat, fix, docs, style, refactor, test, chore)
- Type is followed by
:
and description - Example:
feat: add new login feature
- Types can be:
build
: Changes that affect build system or dependenciesci
: Changes to CI configuration files and scriptschore
: Minor changes (e.g., .gitignore, .pre-commit-config.yaml)docs
: Documentation only changesfeat
: Adding/removing featuresfix
: Bug fixesperf
: Performance improvementsrefactor
: Code changes that neither fix bugs nor add featuresrevert
: Reverting previous commitsstyle
: Changes that don’t affect code meaningtest
: Adding/modifying tests
See more: Conventional Commits
So how do we use Commitlint? Let’s move to the next section.
Pre-commit - Git Hooks Made Easy
Pre-commit helps automate code checking before committing, ensuring code always meets standards before being pushed to the repository.
Install pre-commit (one time):
pip install pre-commit
Configure pre-commit hooks in .pre-commit-config.yaml
:
- Configure Black
repos:
...
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black
language_version: python3.10
- Configure Flake8
repos:
...
- repo: https://github.com/PyCQA/flake8
rev: 7.1.2
hooks:
- id: flake8
language_version: python3.10
- Configure Pylint
repos:
...
- repo: https://github.com/pylint-dev/pylint
rev: v3.3.4
hooks:
- id: pylint
language_version: python3.10
- Configure Commitlint
repos:
...
- repo: https://github.com/opensource-nepal/commitlint
rev: v1.3.0
hooks:
- id: commitlint
Install commit hooks:
pre-commit install
pre-commit install --hook-type commit-msg
After installation, pre-commit will check your code every time you commit and report errors if standards aren’t met.
IDE Configuration
Besides using pre-commit to check code, you can configure your IDE to check code before committing.
VSCode
- Install extension: Black Formatter
- Configure:
{
"python.formatting.provider": "black"
}
flake8
- Install extension: flake8
- Configure:
{
"python.linting.flake8Enabled": true
}
pylint
- Install extension: pylint
- Configure:
{
"python.linting.pylintEnabled": true
}
CI/CD Integration
Integrating checking tools into CI/CD will help you verify code one more time before merging into the main branch. This is especially useful for cases like:
- git commit –no-verify
- adding files on gitlab site
- …
Gitlab CI
Here, I combine format and lint into one job to optimize build time.
The lint stage will run the tools in sequence: black, flake8, pylint, and commitlint, and only runs when the pipeline is a merge request and the target branch is master or release-*
stages:
- lint
- test
- build
- deploy
lint:
image: python:3.10-slim
stage: lint
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^(master|release\-.*)$/ # merge request
environment: $ENVIRONMENT
script:
- pip install -r requirements-dev.txt
- echo "Running linting with black..."
- black --check .
- echo "Running linting with flake8..."
- flake8 .
- echo "Running linting with pylint..."
- pylint .
- echo "Running linting with commitlint..."
- commitlint "$CI_COMMIT_MESSAGE"
cache:
paths:
- .cache/pip
allow_failure: false
Conclusion
Setting up code quality checking tools might take some initial time, but the benefits they bring are well worth it. Not only does it make your code cleaner, but it also helps the entire team work more effectively.
I hope this article helps you set up a professional Python development environment. See you in the next articles! 😊