Introduction to Black
Black is an uncompromising Python code formatter that transforms your code into a consistent style that follows most of the PEP 8 guidelines. Unlike other formatters that offer numerous configuration options, Black takes a "no-decisions" approach, providing minimal configuration to ensure consistency across projects and teams.
This comprehensive guide covers everything you need to know about using Black effectively, from basic usage to advanced integration with development workflows.
The Philosophy of Black
Black's approach to code formatting is fundamentally different from many other tools. Its philosophy can be summarized as:
- Uncompromising: Black doesn't offer many configuration options, deliberately removing the need for formatting debates
- Deterministic: Given the same input code and Black version, the output is always identical
- Speed: Black is designed to be fast, using efficient parsing techniques
- Consistency: All Black-formatted code looks the same, regardless of who wrote it
- Focus on readability: Black's formatting choices prioritize code readability over brevity
As the project's tagline states, Black is "the uncompromising Python code formatter." By providing fewer options, Black eliminates style debates and helps teams focus on code functionality rather than formatting preferences.
Installation
Black can be installed using pip, the Python package manager:
# Using pip (recommended)
pip install black
# Using pipx for isolated installation
pipx install black
# With poetry
poetry add black --dev
# With conda
conda install black -c conda-forge
To install a specific version of Black:
pip install black==23.7.0
Verify the installation:
black --version
Basic Usage
Using Black is straightforward. To format a file or directory:
# Format a single file
black path/to/file.py
# Format a directory
black path/to/directory/
# Format multiple files
black file1.py file2.py file3.py
# Format using pattern matching
black **/*.py
By default, Black will modify files in place. To see what changes would be made without applying them:
black --check path/to/file.py
To see the differences that would be applied:
black --diff path/to/file.py
Command Line Options
Black offers several command-line options to customize its behavior:
# Set the line length (default is 88)
black --line-length 100 file.py
# Skip string normalization
black --skip-string-normalization file.py
# Target specific Python versions
black --target-version py38 file.py
# Format Jupyter notebooks
black --ipynb notebook.ipynb
# Verbose output
black --verbose file.py
# Quiet mode (only error messages)
black --quiet file.py
Configuration
Although Black is intentionally limited in configuration options, you can customize its behavior using a configuration file.
pyproject.toml
Black uses pyproject.toml
for configuration (as specified in PEP 518). Create this file in
your project root:
# pyproject.toml
[tool.black]
line-length = 88
target-version = ['py38']
include = '\\.pyi?$'
extend-exclude = '''
/(
\\.eggs
| \\.git
| \\.hg
| \\.mypy_cache
| \\.tox
| \\.venv
| _build
| buck-out
| build
| dist
)/
'''
skip-string-normalization = false
preview = false
Ignoring Files
To exclude specific files or directories from formatting:
-
Use
extend-exclude
in yourpyproject.toml
as shown above -
Create a
.gitignore
-style file named.blackignore
in your project root -
Add
# fmt: off
and# fmt: on
comments to disable formatting for specific code blocks
Example of using formatting directives in code:
# Regular code that will be formatted
x = { 'a':37,'b':42,
'c':927}
# fmt: off
# This code will not be formatted
x = { 'a':37,'b':42,
'c':927}
# fmt: on
# Regular code that will be formatted again
y = 'hello ''world'
Configuration Options
Black's intentionally limited configuration options include:
Option | Description | Default |
---|---|---|
line-length |
Maximum line length | 88 |
target-version |
Python versions to target | py38, py39, etc. |
include |
Regex patterns of files to include | \\.pyi?$ |
exclude |
Regex patterns of files to exclude | Various build directories |
extend-exclude |
Additional exclusion patterns | None |
skip-string-normalization |
Don't normalize string quotes or prefixes | false |
skip-magic-trailing-comma |
Don't use trailing commas for multiline expressions | false |
preview |
Enable preview features | false |
Formatting Rules
Black enforces a consistent style with specific formatting rules:
Line Length
Black's default line length is 88 characters (rather than PEP 8's recommended 79) for several reasons:
- It's a power of 2 minus a multiple of 2 for aesthetics and git diff alignment
- It's empirically shown to reduce merge conflicts and line count while maintaining readability
- It aligns well with modern screen resolutions and editor layouts
Black will attempt to fit code within the specified line length, but will exceed it when necessary for readability or to avoid breaking syntax.
String Normalization
By default, Black normalizes string quotes:
- Single quotes (
'
) are used for regular strings - Double quotes (
"
) are used when the string contains single quotes or apostrophes - Triple quoted strings maintain their original quote style
This behavior can be disabled with --skip-string-normalization
or the skip-string-normalization = true
configuration option.
Indentation and Line Breaks
Black enforces consistent indentation rules:
- 4 spaces for indentation (never tabs)
- Consistent line break placement for function arguments, list items, etc.
- Trailing commas in multi-line collections
- No extra spaces inside brackets or braces
- Line breaks before binary operators (PEP 8 compliant)
Import Sorting
Black does not sort imports by default. For import sorting, it's recommended to use isort
with
Black-compatible settings:
# In pyproject.toml
[tool.isort]
profile = "black"
multi_line_output = 3
Code Examples
Before and After
Here's an example of Python code before and after formatting with Black:
# Before formatting
def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, 'w') as f:
...
# After formatting with Black
def very_important_function(
template: str,
*variables,
file: os.PathLike,
engine: str,
header: bool = True,
debug: bool = False,
):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, "w") as f:
...
Another example with more complex formatting:
# Before formatting
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# After formatting with Black
def long_function_name(
var_one, var_two, var_three, var_four
):
print(var_one)
Dictionaries and lists are formatted consistently:
# Before formatting
x = { 'a':37,'b':42,
'c':927}
# After formatting with Black
x = {"a": 37, "b": 42, "c": 927}
For longer collections that exceed the line length:
# Before formatting
x = { 'a':37,'b':42,'c':927,'d':765,'e':593,'f':123,'g':456,'h':789,'i':123,'j':987,'k':654,'l':321}
# After formatting with Black
x = {
"a": 37,
"b": 42,
"c": 927,
"d": 765,
"e": 593,
"f": 123,
"g": 456,
"h": 789,
"i": 123,
"j": 987,
"k": 654,
"l": 321,
}
IDE Integration
Black can be integrated with most popular Python IDEs for automatic formatting:
Visual Studio Code
To use Black with VS Code:
- Install the "Python" extension by Microsoft
-
Configure formatting settings in
settings.json
:
{
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--line-length", "88"],
"editor.formatOnSave": true,
"[python]": {
"editor.defaultFormatter": "ms-python.python",
"editor.formatOnSave": true
}
}
PyCharm
For PyCharm integration:
- Install Black through PyCharm's package manager or pip
- Go to Settings → Tools → External Tools → + (Add)
-
Configure the tool with these settings:
- Name: Black
- Program: Full path to Black (e.g.,
$PyInterpreterDirectory$/black
) - Arguments:
$FilePath$
- Working directory:
$ProjectFileDir$
- Optionally set up a keyboard shortcut for the external tool
For automatic formatting on save, consider using the File Watcher plugin.
Vim and Emacs
For Vim users, add Black integration using plugins like ALE or Neoformat:
" Using ALE
let g:ale_fixers = {
\ 'python': ['black'],
\}
let g:ale_fix_on_save = 1
" Using Neoformat
augroup fmt
autocmd!
autocmd BufWritePre *.py Neoformat
augroup END
let g:neoformat_python_black = {
\ 'exe': 'black',
\ 'args': ['-q', '-'],
\ 'stdin': 1,
\ }
let g:neoformat_enabled_python = ['black']
For Emacs users, use the blacken package:
;; Add to your config
(use-package blacken
:ensure t
:hook (python-mode . blacken-mode))
;; With configuration
(setq blacken-line-length '88)
(setq blacken-skip-string-normalization t)
CI/CD Integration
Integrating Black into your CI/CD pipelines ensures consistent code formatting across the project:
GitHub Actions
Here's a GitHub Actions workflow for checking Black formatting:
# .github/workflows/black.yml
name: Black Code Formatter Check
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black
- name: Check formatting with Black
run: |
black --check .
For automated fixes, you can use the Black action:
# .github/workflows/black-action.yml
name: Black Code Formatter
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable
with:
options: "--check --diff --color"
version: "23.7.0"
Pre-commit Hooks
To enforce Black formatting before committing, set up pre-commit hooks:
# Install pre-commit
pip install pre-commit
# Create a .pre-commit-config.yaml file
cat > .pre-commit-config.yaml << EOF
repos:
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
EOF
# Install the git hook scripts
pre-commit install
With this setup, Black will automatically check and format code before each commit.
Troubleshooting
Common Issues
Here are solutions to common Black issues:
- Inconsistent formatting with other tools: Use Black-compatible configurations for tools like isort and flake8
- Formatting breaking code: This usually indicates syntax errors in the original code; Black preserves semantics but requires valid Python
- Line length issues: Black may exceed the specified line length for strings and comments; this is by design
-
Slow performance: For large codebases, use
--fast
mode or target specific directories
Version Conflicts
Black's formatting can change between versions. To ensure consistency:
- Pin the Black version in your requirements or pyproject.toml
- Include Black version in CI configurations
- Use version control hooks to ensure consistent formatting
# In requirements-dev.txt
black==23.7.0
# In pyproject.toml
[build-system]
requires = ["setuptools>=42", "wheel", "black==23.7.0"]
Compatibility with Other Tools
Black with isort
Black doesn't sort imports, so it's common to use it with isort:
# Install both tools
pip install black isort
# Configure isort to be compatible with Black
# In pyproject.toml
[tool.isort]
profile = "black"
multi_line_output = 3
# Run both tools
isort . && black .
Black with Flake8
To use Black with Flake8, you need to adjust some Flake8 rules:
# Install the compatibility plugin
pip install flake8-black
# Configure Flake8
# In .flake8 or setup.cfg
[flake8]
max-line-length = 88
extend-ignore = E203, W503
exclude = .git,__pycache__,build,dist
The E203
and W503
ignores are important because Black intentionally breaks these rules
to follow PEP 8's recommendation for line breaks before binary operators.
Black with mypy
Black is compatible with mypy without any special configuration, but it's important to run Black after mypy in your pipeline because Black might reformat type annotations:
# Correct order
mypy . && black .
# In CI pipelines, check mypy first, then Black
- run: mypy .
- run: black --check .
Conclusion
Black simplifies Python code formatting by making opinionated decisions that prioritize consistency and readability. By eliminating debates over code style, it allows teams to focus on what really matters: writing functionality and fixing bugs.
Despite its "uncompromising" nature, Black has gained widespread adoption in the Python community because it:
- Saves time previously spent on formatting discussions
- Creates consistent, readable code
- Integrates well with the Python ecosystem
- Helps enforce best practices
- Reduces the cognitive load of formatting decisions
Whether you're a solo developer or part of a large team, adopting Black can significantly improve your Python development workflow. By automating formatting decisions, you can focus on writing high-quality, functional code instead of debating style choices.