Linting Excellence: How Black, isort, and Ruff Elevate Python Code Quality
In this article, explore Black, isort, and Ruff to streamline Python code quality checks and ensure consistent coding standards.
Join the DZone community and get the full member experience.
Join For FreeLinting and Its Importance
Q: Can linting make my code better?
A: No. If your logic is not good enough, it cannot help you, but it can surely make it look prettier.
Linting is the process of analyzing code to identify potential errors, code quality issues, and deviations from coding standards. It is a crucial part of modern software development for several reasons:
- Error detection: Linting helps catch bugs and errors early in the development process.
- Code quality: It enforces coding standards, making code more readable and maintainable.
- Consistency: Ensures a uniform coding style across the codebase, which is particularly important in collaborative projects
- Efficiency: Reduces the time spent on code reviews by automatically checking for common issues
Available Tools for Linting and Formatting
Several tools are available for linting and formatting Python code. Among them, the most popular are Black, Ruff, isort, PyLint, and Flake8, to name a few. There are unique strengths and weaknesses for each of the tools and they are also used for a specific purpose. In this article, we will look at Black, Ruff, and isort.
A Glorious Example of How Not to Code
Before diving into the comparison, let's take a look at a sample of poorly written Python code. This will help us illustrate the differences and capabilities of Black, Ruff, and isort.
import datetime
from io import BytesIO
from datetime import datetime
from __future__ import unicode_literals
import os, sys, time
from base64 import b64encode
from PIL import Image, ImageDraw, Image
from flask import Flask, request, redirect, url_for, send_file
from werkzeug.utils import secure_filename
numbers = [1,
2,
4,5,6,
]
MyClass.function(arg1, arg2, arg3, flag, option)
def my_func(some_data: list, *args, path: os.PathLike, name: str, verbosity: bool = True, quiet: bool = False):
"""Processes `data` using `args` and saves to `path`."""
with open(path, 'a') as file:
...
if first_condititon \
and second_condition:
...
Black
Features
Black performs in-place code style changes with a prime focus on the following:
- Opinionated (e.g., Spaces over Tabs)
- PEP8 Compliance [See Pragmatism]
- Smallest possible diff
- Stability: Black has minimal to no configuration parameters, to ensure code style consistency.
- Post-processing AST checks to ensure no change in logic. Optionally you can turn it off by using the
–fast
option.
Installation
Install Black by running this command:
pip install black
Example Usage
black [options] <SOURCE_FOLDER-or-FILE>
See black --help
for more details.
How Did It Perform?
import datetime
from io import BytesIO
from datetime import datetime
from __future__ import unicode_literals
import os, sys, time
from base64 import b64encode
from PIL import Image, ImageDraw, Image
from flask import Flask, request, redirect, url_for, send_file
from werkzeug.utils import secure_filename
numbers = [
1,
2,
4,
5,
6,
]
MyClass.function(arg1, arg2, arg3, flag, option)
def my_func(
some_data: list,
*args,
path: os.PathLike,
name: str,
verbosity: bool = True,
quiet: bool = False
):
"""Processes `data` using `args` and saves to `path`."""
with open(path, "a") as file:
...
if first_condititon and second_condition:
...
P.S. Notice how it did not sort/format the imports.
isort
Features
isort prioritizes import organization with a primary focus on:
- Sorting: Sorts the imports alphabetically
- Sections: Groups the imports into sections and by type
- Multi-line imports: Arranges the multi-line imports into a balanced grid
- Add/Remove imports:
isort
can be run or configured to add/removeimport
s automatically.
Installation
Install isort by running this command:
pip install isort
Example Usage
isort [OPTIONS] <SOURCE_FOLDER-or-FILE>
See isort --help
for more details.
How Did It Perform?
from __future__ import unicode_literals
import datetime
import os
import sys
import time
from base64 import b64encode
from datetime import datetime
from io import BytesIO
from flask import Flask, redirect, request, send_file, url_for
from PIL import Image, ImageDraw
from werkzeug.utils import secure_filename
numbers = [1,
2,
4,5,6,
]
MyClass.function(arg1, arg2, arg3, flag, option)
def my_func(some_data: list, *args, path: os.PathLike, name: str, verbosity: bool = True, quiet: bool = False):
"""Processes `data` using `args` and saves to `path`."""
with open(path, 'a') as file:
...
if first_condititon \
and second_condition:
...
P.S. Notice how the code was not formatted.
Ruff
Features
Ruff performs comprehensive linting and autofixes, adding type hints, and ensuring code quality and consistency.
- Linting: Performs a wide range of linting checks
- Autofix: Can automatically fix many issues
- Integration: Easy to integrate with other tools such as
isort
- Configuration: Supports configuration via
pyproject.toml
or command-line flags.
Installation
Install Ruff by running this command:
pip install ruff
Example Usage
- For linting:
ruff check [OPTIONS] <SOURCE_FOLDER-or-FILE>
- For formatting:
ruff format [OPTIONS] <SOURCE_FOLDER-or-FILE>
See ruff --help
for more details.
Note: Ruff does not automatically sort imports. In order to do this, run the following:
ruff check --select I --fix
ruff format
How Did It Perform?
from __future__ import unicode_literals
import datetime
import os
import sys
import time
from base64 import b64encode
from datetime import datetime
from io import BytesIO
from flask import Flask, redirect, request, send_file, url_for
from PIL import Image, ImageDraw
from werkzeug.utils import secure_filename
numbers = [
1,
2,
4,
5,
6,
]
MyClass.function(arg1, arg2, arg3, flag, option)
def my_func(
some_data: list,
*args,
path: os.PathLike,
name: str,
verbosity: bool = True,
quiet: bool = False,
):
"""Processes `data` using `args` and saves to `path`."""
with open(path, "a") as file:
...
if first_condititon and second_condition:
...
Where Do They Stand?
black |
isort |
ruff |
|
---|---|---|---|
Purpose |
Code formatter |
Import sorter and formatter |
Linter and formatter |
Speed |
Fast |
Fast |
Extremely fast |
Primary Functionality |
Formats Python code to a consistent style |
Sorts and formats Python imports |
Lints Python code and applies autofixes |
Configuration |
pyproject.toml |
pyproject.toml, .isort.cfg, setup.cfg |
pyproject.toml or command-line flags |
Ease of Use |
High |
High |
High |
Popularity |
Very high |
High |
Increasing |
Pros |
Extensive, opinionated styling |
Import grouping and sectioning for improved readability |
Faster than most linters; developed on Rust |
Cons |
May not have extensive styling rules like pylint |
- |
Supports all F Rules from Flake8, although, Missing a majority of E rules |
Conclusion
Black, Ruff, and isort are powerful tools that help maintain high code quality in Python projects. Each tool has its specific strengths, making them suitable for different aspects of code quality:
- Black: Best for automatic code formatting and ensuring a consistent style
- isort: Perfect for organizing and formatting import statements
- Ruff: Ideal for comprehensive linting and fixing code quality issues quickly
By understanding the unique features and benefits of each tool, developers can choose the right combination to fit their workflow and improve the readability, maintainability, and overall quality of their codebase.
Opinions expressed by DZone contributors are their own.
Comments