Beginner
Code reviews should be about logic, architecture, and correctness — not about whether you put a space before a colon or how you sorted your imports. But without automated formatting, every team spends time on style debates, inconsistent diffs pollute git history, and onboarding new developers is a friction-filled process. Black and isort are the two tools that eliminate this problem entirely: Black reformats your Python code in one consistent opinionated style, and isort keeps your imports sorted and organized. Combined, they handle the vast majority of Python style decisions automatically.
Black calls itself “the uncompromising code formatter.” It has almost no configuration options by design — the goal is for every Black-formatted project to look the same, so developers can read any Python project without adjusting to a new style. isort sorts imports alphabetically within their sections (standard library, third-party, local), keeping them clean and diff-friendly. Install both with pip install black isort.
In this article, we will cover: how to use Black to format Python files from the command line, how to configure Black’s line length and target Python version, how isort organizes imports, how to combine Black and isort without conflicts, how to run both as pre-commit hooks so formatting is automatic on every commit, and how to integrate them into CI. By the end, you will have a fully automated formatting pipeline that requires zero style decisions from your team.
Black Quick Example
Here is some unformatted Python code and what Black does to it:
# unformatted.py (before Black)
import sys,os
def calculate(x,y, z):
result=x+y*z
if result>100: return True
else:
return False
my_list=[1,2, 3,4,
5,6]
d={'key1':'value1','key2': 'value2','key3':'value3'}
Run black unformatted.py and the file becomes:
# unformatted.py (after Black)
import sys, os
def calculate(x, y, z):
result = x + y * z
if result > 100:
return True
else:
return False
my_list = [
1,
2,
3,
4,
5,
6,
]
d = {"key1": "value1", "key2": "value2", "key3": "value3"}
Black added consistent spacing, expanded the list to one-item-per-line (because it exceeded the line length), kept the dictionary on one line (because it fits), and enforced double quotes. Notice it also changed import sys,os but did not sort them — that is isort’s job.
What Is Black and What Does It Enforce?
Black is a PEP 8-compliant code formatter that enforces a specific subset of style choices. Unlike flake8 (which only reports style violations), Black actually rewrites your code. It makes these decisions for you:
| Style Aspect | Black’s Choice | Reason |
|---|---|---|
| Quotes | Double quotes always | Consistency; avoids escaping |
| Trailing commas | Added in multi-line structures | Cleaner git diffs |
| Line length | 88 characters (configurable) | Slightly more than PEP 8’s 79 |
| Blank lines | 2 between top-level, 1 between methods | PEP 8 standard |
| Magic trailing comma | Respects it — keeps multi-line if comma present | Developer intent preserved |
The key insight is that Black removes the decision-making burden from developers. You do not debate whether to use single or double quotes — Black uses double. You do not argue about line wrapping — Black wraps at 88 characters. Once your team agrees to use Black, style debates disappear from code reviews.
Using Black on the Command Line
Black’s command-line interface is straightforward. You can format a single file, a directory, or use --check to preview what would change without modifying files.
# Install Black
# pip install black
# Format a single file (modifies in place)
# black my_script.py
# Format an entire directory
# black src/
# Check what would change (exit 1 if changes needed)
# black --check src/
# Show the diff without applying changes
# black --diff my_script.py
# Format with a custom line length (79 for strict PEP 8)
# black --line-length 79 src/
# Target a specific Python version
# black --target-version py311 src/
The --check flag is what you use in CI pipelines — it returns exit code 1 if any files need reformatting, which fails the CI build. This forces developers to run Black locally before pushing. The --diff flag shows exactly what would change, which is useful for understanding Black’s decisions.
# pyproject.toml -- configure Black project-wide
[tool.black]
line-length = 88
target-version = ['py311']
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.venv
| build
| dist
)/
'''
Put pyproject.toml in your project root and Black will use these settings automatically. The target-version setting tells Black which Python syntax features are available — this affects magic trailing comma behavior and some string formatting decisions.
Using isort to Organize Imports
isort sorts Python imports into three sections separated by blank lines: standard library imports, third-party imports, and local imports. Within each section, it sorts alphabetically. This matches PEP 8 and makes diffs clean — changing one import only affects one line.
# messy_imports.py (before isort)
import json
from flask import Flask, request
import os
from myapp.models import User
import sys
from datetime import datetime
import requests
from myapp.utils import format_date
After running isort messy_imports.py:
# messy_imports.py (after isort)
import json
import os
import sys
from datetime import datetime
import requests
from flask import Flask, request
from myapp.models import User
from myapp.utils import format_date
Standard library imports (json, os, sys, datetime) come first. Third-party imports (requests, flask) come second. Local imports (myapp.*) come last. Within each section, everything is alphabetically sorted. The blank lines between sections are isort’s signature — they make the import structure visually clear.
Making Black and isort Work Together
Black and isort can conflict: Black sometimes reformats lines that isort just organized. The fix is to tell isort to use Black-compatible settings. isort has a built-in --profile black option that makes them cooperate perfectly.
# pyproject.toml -- configure isort for Black compatibility
[tool.isort]
profile = "black"
line_length = 88
With this configuration, isort will format multi-line imports the same way Black would. Run them in order — isort first, then Black — or use a pre-commit hook that runs both:
# Run both tools on the src/ directory
# isort src/
# black src/
# Verify both are satisfied (for CI)
# isort --check-only src/ && black --check src/
Automating with Pre-Commit Hooks
The most effective way to use Black and isort is as pre-commit hooks — they run automatically every time you commit code, so formatting is never forgotten. The pre-commit framework makes this easy.
# Install pre-commit
# pip install pre-commit
# .pre-commit-config.yaml -- put this in your project root
repos:
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
# Initialize pre-commit (run once after creating the config)
# pre-commit install
# Run manually on all files
# pre-commit run --all-files
After pre-commit install, every git commit automatically runs Black, isort, and flake8 on the staged files. If any formatting changes are needed, the commit is blocked and the files are auto-fixed — you just git add the changes and commit again. This means formatting violations never reach the repository.
Real-Life Example: Setting Up a Full Python Project
Here is a complete project setup script that installs Black, isort, and pre-commit and configures them to work together:
# project_setup.py
"""
Script to set up Black + isort + pre-commit for a Python project.
Run from your project root directory.
"""
import subprocess
import sys
from pathlib import Path
def run(cmd):
"""Run a shell command and print output."""
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.stdout:
print(result.stdout)
if result.returncode != 0:
print(f"ERROR: {result.stderr}")
return result.returncode == 0
# Install tools
run([sys.executable, '-m', 'pip', 'install', 'black', 'isort', 'pre-commit', '--quiet'])
# Write pyproject.toml config
pyproject = Path('pyproject.toml')
if not pyproject.exists():
pyproject.write_text('''[tool.black]
line-length = 88
target-version = ['py311']
[tool.isort]
profile = "black"
line_length = 88
''')
print("Created pyproject.toml")
# Write pre-commit config
precommit = Path('.pre-commit-config.yaml')
precommit.write_text('''repos:
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
''')
print("Created .pre-commit-config.yaml")
# Install pre-commit hooks
if Path('.git').exists():
run(['pre-commit', 'install'])
print("pre-commit hooks installed!")
else:
print("Not a git repo -- skipping pre-commit install")
print("Run 'git init' first, then 'pre-commit install'")
print("\nSetup complete! Run 'pre-commit run --all-files' to format existing code.")
Output:
Running: /usr/bin/python3 -m pip install black isort pre-commit --quiet
Created pyproject.toml
Created .pre-commit-config.yaml
pre-commit hooks installed!
Setup complete! Run 'pre-commit run --all-files' to format existing code.
This script sets up the entire formatting pipeline in under a minute. Once it runs, every commit in the project will be automatically formatted by Black and isort with no developer effort. You can extend the pre-commit config to add mypy for type checking or bandit for security scanning.
Frequently Asked Questions
How is Black different from autopep8?
autopep8 fixes PEP 8 violations but tries to be minimally invasive — it only changes what it must. Black is more opinionated and makes more sweeping changes to ensure a consistent style everywhere. Black produces deterministic output (running it twice gives the same result), while autopep8 may not. Most teams prefer Black because it eliminates more style decisions and produces cleaner diffs.
How do I introduce Black to a large existing codebase?
Run black . --diff first to see the scope. Then run black . in a single dedicated commit with the message “Apply Black formatting” — this isolates all formatting changes from logic changes. Configure git to exclude this commit from git blame with git blame --ignore-rev <commit-hash> or add the hash to .git-blame-ignore-revs. From that point, all new commits will be incrementally formatted.
Can I skip Black formatting for specific code?
Yes. Wrap code with # fmt: off and # fmt: on to disable Black for a specific block. This is useful for manually aligned code like lookup tables or matrix definitions where Black’s formatting would hurt readability. Use it sparingly — the value of Black comes from it being consistently applied everywhere.
How do I fail CI if code is not formatted?
Run black --check . and isort --check-only . in your CI pipeline. Both commands return exit code 1 if any files need formatting, which fails the CI build. In GitHub Actions, add a formatting job that runs before your tests. When it fails, developers must run Black and isort locally before the CI passes.
How do I set up VS Code to auto-format with Black?
Install the Black Formatter extension from the VS Code marketplace. Then in your settings.json: set "editor.formatOnSave": true, "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" }, and install the isort extension for import sorting. Now every time you save a Python file, Black and isort run automatically.
Conclusion
We covered the complete Black and isort workflow: formatting files with black and isort on the command line, configuring both tools via pyproject.toml, using --profile black to make isort Black-compatible, automating everything with pre-commit hooks, and checking formatting in CI with --check flags. The project setup script ties it all together into a one-command installation.
The cumulative benefit of Black and isort is significant — teams report that code review time drops noticeably when formatting is no longer a discussion topic. Developers spend more mental energy on logic and less on whitespace. New contributors can get up to speed faster because the formatting standards are enforced automatically rather than documented in a style guide.
Official documentation: black.readthedocs.io and pycqa.github.io/isort