Black
DevOps Tool
Black
Overview
Black is Python's "uncompromising" code formatter that automatically applies PEP 8 compliant code style, eliminating formatting discussions. Adopting an "opinionated" approach that intentionally minimizes configuration options and forcibly applies consistent code style. The 2025 version, Black 25.1.0, introduces a new stable style with enhanced formatting features including Unicode character normalization, improved type annotations, and enhanced docstring detection.
Details
Black is a Python-specific code formatter managed by the Python Software Foundation (PSF), established since its 2018 release as the tool to "end all code formatting debates" in Python development. Black 25.1.0 in 2025 introduces a new stable style (2025 stable style) providing more sophisticated formatting capabilities.
Key Features
- Uncompromising Formatting: Intentionally minimizes configuration options, emphasizing consistency
- Full PEP 8 Compliance: Strictly follows Python's official style guide
- High-Speed Execution: Achieves fast processing through partial Rust-based implementation
- Jupyter Notebook Support: Automatic code cell formatting in Jupyter environments
- Editor Integration: Support for major editors like VS Code, PyCharm, Vim, etc.
- CI/CD Integration: Automated checking and fixing in GitHub Actions, GitLab CI, etc.
- Deterministic Output: Same results with multiple executions, complete idempotency
- Minimal Configuration: Limited configuration options via pyproject.toml
2025 New Features
- Unicode Character Normalization: Lowercase normalization of Unicode escape characters in strings
- Improved Docstring Detection: Enhanced consistency in docstring recognition
- Enhanced Type Annotation Formatting: Automatic addition of trailing commas to function parameters
- Case Statement Optimization: Removal of redundant parentheses in if guards and line length control
- Improved Comment Processing: Stop normalizing whitespace before
# fmt: skip
comments
Pros and Cons
Pros
- Immediate style unification across Python development teams without configuration
- Complete elimination of formatting discussions and code review time
- Generation of standard, readable Python code through PEP 8 compliance
- Practical execution time for large projects through high-speed processing
- Stable formatting results through deterministic output
- Support for scientific computing and data science environments through Jupyter Notebook integration
- Real-time auto-formatting experience through editor integration
- Reduced learning costs and maintenance burden through minimal configuration
- Automated quality assurance and code quality gates through CI/CD integration
- Language-optimized formatting through Python-specific specialization
Cons
- Extreme limitation of customization (intentional design but lacks flexibility)
- Massive changes when applying to existing code, causing increased diffs
- Forced divergence from existing personal/team coding habits
- Difficulty supporting specific formatting requirements (corporate standards, etc.)
- Limited adjustment of line length restrictions (default 88 characters)
- Risk of massive changes when bulk-applying to legacy codebases
- Complexity of integration settings in some editors
- Inability to use unified tools in multilingual projects due to Python-only focus
- Difficulty in fine-grained control over formatting results
- Restriction of developer expression freedom through excessive opinion enforcement
Reference Links
- Black Official Site
- Black GitHub Repository
- Black PyPI
- Black Playground
- PEP 8 Style Guide
- pre-commit hooks
Code Examples
Installation and Basic Setup
# Basic installation
pip install black
# Installation with Jupyter Notebook support
pip install "black[jupyter]"
# Specific version installation
pip install black==25.1.0
# Add as development dependency
pip install --save-dev black
# Poetry dependency management
poetry add --group dev black
# Add to requirements.txt
echo "black>=25.1.0" >> requirements-dev.txt
# Verify installation
black --version
black --help
Basic Execution Methods
# Format single file
black script.py
# Format entire directory
black src/
# Multiple pattern specification
black src/ tests/ scripts/
# Check mode (verify without changes)
black --check src/
# Show diff (verify changes)
black --diff src/
# Verbose output
black --verbose src/
# Quiet mode (output only on errors)
black --quiet src/
# Exclude specific files
black src/ --exclude="migrations|__pycache__|\.venv"
# Specify line length
black --line-length 100 src/
# Jupyter Notebook support
black notebook.ipynb
Configuration File (pyproject.toml)
# pyproject.toml - Black configuration
[tool.black]
line-length = 88 # Line length setting (default 88)
target-version = ['py39', 'py310', 'py311', 'py312'] # Target Python versions
include = '\.pyi?$' # Target file patterns
extend-exclude = '''
# Exclusion patterns (regex)
/(
# Directory exclusions
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| migrations
# File exclusions
| foo.py
)/
'''
# Enable preview features
preview = true
# Disable string normalization (maintain single quotes)
skip-string-normalization = false
# Disable magic trailing comma
skip-magic-trailing-comma = false
# Experimental features
experimental-string-processing = false
# Jupyter notebook configuration
jupyter = true
Editor Integration Settings
VS Code Configuration
// .vscode/settings.json
{
"python.formatting.provider": "black",
"python.formatting.blackArgs": [
"--line-length=88",
"--target-version=py312"
],
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
}
// Black extension configuration
{
"black-formatter.args": [
"--line-length=88",
"--preview"
],
"black-formatter.importStrategy": "fromEnvironment"
}
PyCharm Configuration
# PyCharm External Tool configuration
# File → Settings → Tools → External Tools → Add
Name: Black
Description: Black Python formatter
Program: black
Arguments: $FilePath$
Working Directory: $ProjectFileDir$
# Or configure as File Watcher
# File Type: Python
# Scope: Project Files
# Program: black
# Arguments: $FilePath$
pre-commit Integration
# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.1.0
hooks:
- id: black
language_version: python3.12
args: [--line-length=88, --target-version=py312]
# Jupyter Notebook support
- id: black-jupyter
language_version: python3.12
args: [--line-length=88, --target-version=py312]
# Combine with other hooks
- 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
args: [--extend-ignore=E203,W503,E501]
# pre-commit setup
pip install pre-commit
pre-commit install
pre-commit autoupdate
# Manual execution
pre-commit run black --all-files
pre-commit run --all-files
CI/CD Integration Examples
GitHub Actions
# .github/workflows/black.yml
name: Black Code Formatter
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
black:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Black
run: pip install black[jupyter]==25.1.0
- name: Check formatting with Black
run: black --check --diff src/
- name: Format code with Black (if check fails)
if: failure()
run: black src/
- name: Commit formatting changes
if: failure() && github.event_name == 'pull_request'
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git add -A
git commit -m "Apply Black formatting" -a || exit 0
git push
tox Integration
# tox.ini
[tox]
envlist = py39,py310,py311,py312,black,flake8
[testenv:black]
deps = black[jupyter]==25.1.0
commands = black --check --diff src/ tests/
[testenv:black-format]
deps = black[jupyter]==25.1.0
commands = black src/ tests/
# Makefile integration
format:
black src/ tests/
isort src/ tests/
check:
black --check --diff src/ tests/
isort --check-only src/ tests/
flake8 src/ tests/
Integration with Other Tools
isort Integration
# pyproject.toml - isort configuration
[tool.isort]
profile = "black" # Black-compatible profile
line_length = 88
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
flake8 Integration
# .flake8 or setup.cfg
[flake8]
max-line-length = 88
extend-ignore =
# Ignore rules that conflict with Black
E203, # whitespace before ':'
W503, # line break before binary operator
E501 # line too long (handled by Black)
mypy Integration
# pyproject.toml - mypy configuration
[tool.mypy]
python_version = "3.12"
strict = true
# Compatibility with Black-formatted code
show_error_codes = true
pretty = true
color_output = true
Jupyter Notebook Integration
# Jupyter Notebook specific formatting
black notebook.ipynb
# Multiple notebooks
black notebooks/
# Install Jupyter Lab extension
pip install jupyterlab-code-formatter black
# JupyterLab configuration
jupyter lab --generate-config
# Using Black within Jupyter
# Magic command
%load_ext lab_black
# Auto-format cell
%%black
def poorly_formatted_function(x,y,z):
return x+y+z
# Result: automatically formatted
def poorly_formatted_function(x, y, z):
return x + y + z
Actual Formatting Examples
Before Formatting
# Example of poor formatting
def calculate_total(items,tax_rate=0.1,discount=0):
total=0
for item in items:
price=item.get('price',0)
quantity=item.get('quantity',1)
total+=price*quantity
tax=total*tax_rate
final_total=total+tax-discount
return final_total
class ProductManager:
def __init__(self,products=[]):
self.products=products
def add_product(self,name,price,category='general'):
product={'name':name,'price':price,'category':category}
self.products.append(product)
def get_products_by_category(self,category):
return [p for p in self.products if p['category']==category]
After Formatting
# Formatted by Black
def calculate_total(items, tax_rate=0.1, discount=0):
total = 0
for item in items:
price = item.get("price", 0)
quantity = item.get("quantity", 1)
total += price * quantity
tax = total * tax_rate
final_total = total + tax - discount
return final_total
class ProductManager:
def __init__(self, products=None):
self.products = products or []
def add_product(self, name, price, category="general"):
product = {"name": name, "price": price, "category": category}
self.products.append(product)
def get_products_by_category(self, category):
return [p for p in self.products if p["category"] == category]
Advanced Configuration and Workflows
# Makefile integration example
.PHONY: format check test
format:
black src/ tests/
isort src/ tests/
check:
black --check --diff src/ tests/
isort --check-only --diff src/ tests/
flake8 src/ tests/
mypy src/
test: check
pytest tests/ -v --cov=src/
ci: format test
echo "CI complete"
# Development script
#!/bin/bash
# scripts/format.sh
set -e
echo "Formatting Python code with Black..."
black src/ tests/
echo "Sorting imports with isort..."
isort src/ tests/
echo "Checking with flake8..."
flake8 src/ tests/
echo "Type checking with mypy..."
mypy src/
echo "Running tests..."
pytest tests/
echo "✅ All checks passed!"