Black

コード品質フォーマッターPythonDevOps自動整形PEP8妥協しない

DevOpsツール

Black

概要

Blackは、Pythonの「妥協しない」コードフォーマッターです。PEP 8準拠のコードスタイルを自動適用し、フォーマットに関する議論を排除。設定オプションを最小限に抑制し、一貫性のあるコードスタイルを強制的に適用する「Opinionated」なアプローチを採用。2025年版のBlack 25.1.0では新しい安定スタイルが導入され、Unicode文字正規化、型注釈の改善、docstring検出の強化など、より洗練されたフォーマット機能を提供しています。

詳細

Black(ブラック)は、Python Software Foundation(PSF)が管理するPython専用コードフォーマッターで、2018年のリリース以来、Python開発において「コードフォーマットの議論を終わらせる」ツールとして確立されています。2025年版のBlack 25.1.0では、新しい安定スタイル(2025 stable style)が導入され、より洗練されたフォーマット機能を提供します。

主要な特徴

  • 妥協しないフォーマット: 設定オプションを意図的に最小化し、一貫性を重視
  • PEP 8完全準拠: Pythonの公式スタイルガイドに厳密に従う
  • 高速実行: Rustベースの部分的実装により高速処理を実現
  • Jupyter Notebook対応: Jupyter環境でのコードセル自動フォーマット
  • エディター統合: VS Code、PyCharm、Vim等の主要エディター対応
  • CI/CD統合: GitHub Actions、GitLab CI等での自動チェック・修正
  • 決定論的出力: 何度実行しても同じ結果、完全な冪等性
  • 最小設定: pyproject.tomlでの限定的な設定オプション

2025年新機能

  • Unicode文字正規化: 文字列内のUnicodeエスケープ文字の小文字正規化
  • 改善されたdocstring検出: docstring認識の一貫性向上
  • 型注釈フォーマット強化: 関数パラメータの末尾カンマ自動追加
  • case文の最適化: if文ガードの冗長な括弧除去と行長制御
  • コメント処理改善: # fmt: skipコメント前の空白正規化を停止

メリット・デメリット

メリット

  • 設定不要でPython開発チーム全体の即座なスタイル統一
  • フォーマットに関する議論とコードレビュー時間の完全排除
  • PEP 8準拠による標準的で読みやすいPythonコード生成
  • 高速処理により大規模プロジェクトでも実用的な実行時間
  • 決定論的出力による安定したフォーマット結果
  • Jupyter Notebook統合による科学計算・データサイエンス環境対応
  • エディター統合によるリアルタイム自動フォーマット体験
  • 最小設定により学習コストの削減とメンテナンス負荷軽減
  • CI/CD統合による自動品質保証とコード品質ゲート実現
  • Python専用特化による言語仕様に最適化されたフォーマット

デメリット

  • カスタマイズ性の極度な制限(意図的設計だが柔軟性に欠ける)
  • 既存コードへの適用時の大幅な変更によるdiff増大
  • 個人・チームの既存コーディング習慣との強制的な乖離
  • 特定のフォーマット要求(企業標準等)への対応困難
  • 行長制限(デフォルト88文字)の調整余地限定
  • レガシーコードベースでの一括適用時の大量変更リスク
  • 一部エディターでの統合設定の複雑さ
  • Python専用のため多言語プロジェクトでの統一ツール使用不可
  • フォーマット結果への細かい制御が困難
  • 過度なOpinion強制による開発者の表現自由度制限

参考ページ

書き方の例

インストールと基本セットアップ

# 基本インストール
pip install black

# Jupyter Notebook対応インストール
pip install "black[jupyter]"

# 特定バージョン指定
pip install black==25.1.0

# 開発依存として追加
pip install --save-dev black

# 詩人の依存管理(Poetry)
poetry add --group dev black

# requirements.txtに追加
echo "black>=25.1.0" >> requirements-dev.txt

# 動作確認
black --version
black --help

基本的な実行方法

# 単一ファイルのフォーマット
black script.py

# ディレクトリ全体のフォーマット
black src/

# 複数パターン指定
black src/ tests/ scripts/

# チェックモード(変更せずに確認)
black --check src/

# 差分表示(変更内容確認)
black --diff src/

# 詳細出力
black --verbose src/

# 静観モード(エラー時のみ出力)
black --quiet src/

# 特定ファイル除外
black src/ --exclude="migrations|__pycache__|\.venv"

# ラインレングス指定
black --line-length 100 src/

# Jupyter Notebook対応
black notebook.ipynb

設定ファイル(pyproject.toml)

# pyproject.toml - Black設定
[tool.black]
line-length = 88                    # 行長設定(デフォルト88)
target-version = ['py39', 'py310', 'py311', 'py312']  # 対象Pythonバージョン
include = '\.pyi?$'                 # 対象ファイルパターン
extend-exclude = '''
# 除外パターン(正規表現)
/(
  # ディレクトリ除外
    \.eggs
  | \.git
  | \.hg
  | \.mypy_cache
  | \.tox
  | \.venv
  | _build
  | buck-out
  | build
  | dist
  | migrations
  
  # ファイル除外
  | foo.py
)/
'''

# プレビュー機能有効化
preview = true

# 文字列正規化無効化(シングルクォート維持)
skip-string-normalization = false

# マジックトレーリングカンマ無効化
skip-magic-trailing-comma = false

# 実験的機能
experimental-string-processing = false

# jupyter notebook設定
jupyter = true

エディター統合設定

VS Code設定

// .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拡張機能設定
{
  "black-formatter.args": [
    "--line-length=88",
    "--preview"
  ],
  "black-formatter.importStrategy": "fromEnvironment"
}

PyCharm設定

# PyCharm External Tool設定
# File → Settings → Tools → External Tools → Add

Name: Black
Description: Black Python formatter
Program: black
Arguments: $FilePath$
Working Directory: $ProjectFileDir$

# またはFile Watcherとして設定
# File Type: Python
# Scope: Project Files
# Program: black
# Arguments: $FilePath$

pre-commit統合

# .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対応
      - id: black-jupyter
        language_version: python3.12
        args: [--line-length=88, --target-version=py312]

  # その他のhookと組み合わせ
  - 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設定
pip install pre-commit
pre-commit install
pre-commit autoupdate

# 手動実行
pre-commit run black --all-files
pre-commit run --all-files

CI/CD統合例

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統合

# 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統合
format:
	black src/ tests/
	isort src/ tests/

check:
	black --check --diff src/ tests/
	isort --check-only src/ tests/
	flake8 src/ tests/

他ツールとの統合

isortとの連携

# pyproject.toml - isort設定
[tool.isort]
profile = "black"                    # Blackと互換性のあるプロファイル
line_length = 88
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true

flake8との連携

# .flake8 または setup.cfg
[flake8]
max-line-length = 88
extend-ignore = 
    # Blackと競合するルールを無視
    E203,  # whitespace before ':'
    W503,  # line break before binary operator
    E501   # line too long (handled by Black)

mypy統合

# pyproject.toml - mypy設定
[tool.mypy]
python_version = "3.12"
strict = true

# Blackでフォーマットされたコードとの互換性
show_error_codes = true
pretty = true
color_output = true

Jupyter Notebook統合

# Jupyter Notebook専用フォーマット
black notebook.ipynb

# 複数ノートブック
black notebooks/

# Jupyter Lab拡張機能インストール
pip install jupyterlab-code-formatter black

# JupyterLab設定
jupyter lab --generate-config
# Jupyter内でのBlack使用
# マジックコマンド
%load_ext lab_black

# セル自動フォーマット
%%black
def poorly_formatted_function(x,y,z):
    return x+y+z

# 結果: 自動的にフォーマットされる
def poorly_formatted_function(x, y, z):
    return x + y + z

実際のフォーマット例

フォーマット前

# 悪いフォーマットの例
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]

フォーマット後

# 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]

高度な設定とワークフロー

# Makefileでの統合例
.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"

# 開発用スクリプト
#!/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!"