Black ile Python Kod Formatlama ve Standart Belirleme

Bir Python projesinde ilk kez “bu dosyayı sen mi formatladin?” tartışmasını yaşadığınızda, kod kalitesi araçlarının neden bu kadar önemli olduğunu anlıyorsunuz. Ekipte beş kişi var, beş farklı editör, beş farklı alışkanlık ve sonuçta beş farklı kod stili. Code review’lar teknik tartışmalar yerine “neden burada boşluk bırakmışsın?” savaşlarına dönüşüyor. İşte tam bu noktada Black devreye giriyor.

Black Nedir ve Neden Farklı?

Black, Python için “opinionated” bir kod formatlayıcı. Bu kelimeyi özellikle Türkçeye çevirmiyorum çünkü tam karşılığı şu: Black sizinle pazarlık etmez. Yapılandırma seçenekleri son derece sınırlı, çünkü tasarım felsefesi gereği “tek doğru format” anlayışını benimsiyor.

PEP 8 uyumlu formatlayıcılar olan autopep8 veya yapf ile karşılaştırdığımda Black’in en büyük avantajı şu: bir dosyayı formatladığınızda, aynı dosyayı tekrar formatlasanız da hiçbir şey değişmez. Idempotent bir araç bu. CI/CD pipeline’ınızda çalıştırıyorsunuz, sonuç her zaman aynı.

Kurulum basit:

pip install black

# Belirli bir versiyonu pinlemek için (production ortamları için önerilir)
pip install black==23.12.1

# Jupyter notebook desteği için
pip install "black[jupyter]"

Temel Kullanım: İlk Adımlar

Black’i çalıştırmanın en basit yolu doğrudan bir dosya veya dizin üzerine uygulamak:

# Tek dosya formatla
black my_script.py

# Tüm proje dizinini formatla
black .

# Formatlamadan önce ne değişeceğini gör (dry-run)
black --check .

# Değişiklikleri diff formatında göster
black --diff my_script.py

# Birden fazla dosya
black src/ tests/ scripts/

--check parametresi özellikle CI/CD pipeline’larında kritik. Kodu değiştirmeden sadece “bu dosya Black standartlarına uygun mu?” sorusunu soruyor. Uygun değilse exit code 1 döndürüyor ve pipeline’ınız başarısız oluyor. Bu tam istediğimiz davranış.

Şimdi gerçek bir örnek görelim. Aşağıdaki kod parçasını ele alalım:

# formatlanmamis_kod.py
def hesapla_vergi(gelir,vergi_orani,muafiyet_miktari=0):
    if gelir>muafiyet_miktari:
        vergi = (gelir-muafiyet_miktari)*vergi_orani
        return vergi
    else:
          return 0

sonuc = hesapla_vergi(50000,0.15,8000)
print(f"Vergi: {sonuc}")

Black bu dosyayı çalıştırdıktan sonra:

# black sonrasi
def hesapla_vergi(gelir, vergi_orani, muafiyet_miktari=0):
    if gelir > muafiyet_miktari:
        vergi = (gelir - muafiyet_miktari) * vergi_orani
        return vergi
    else:
        return 0


sonuc = hesapla_vergi(50000, 0.15, 8000)
print(f"Vergi: {sonuc}")

Operatör etrafındaki boşluklar, girinti tutarlılığı, fonksiyon sonrası iki boş satır kuralı, hepsi otomatik düzenlendi.

pyproject.toml ile Yapılandırma

Black’in yapılandırma seçenekleri kasıtlı olarak kısıtlı tutulmuş olsa da bazı temel ayarlar yapılabilir. Modern Python projelerinde pyproject.toml standart haline geldi:

# pyproject.toml
[tool.black]
line-length = 88
target-version = ['py39', 'py310', 'py311']
include = '.pyi?$'
extend-exclude = '''
/(
  # Otomatik üretilen dosyalar
  | migrations
  | .git
  | .venv
  | build
  | dist
  | __pycache__
)/
'''

line-length: Varsayılan 88 karakter. PEP 8’in 79 karakter önerisiyle çelişiyor gibi görünse de Black’in ekibi bunu bilinçli seçmiş. Modern ekranlar ve editörler düşünüldüğünde 88 daha pratik bir değer.

target-version: Black’e hangi Python versiyonlarını hedeflediğinizi söylüyorsunuz. Bu bazı syntax kararlarını etkiliyor.

extend-exclude: Django projelerinde migrations klasörünü kesinlikle dışarıda bırakın. Otomatik üretilen migration dosyalarını Black ile formatlamaya çalışmak baş ağrısından başka bir şey değil.

Pre-commit Hook Entegrasyonu

Kod formatlama araçlarını lokal geliştirme ortamına entegre etmenin en etkili yolu pre-commit hook’ları. Commit yapmadan önce Black otomatik çalışıyor ve formatlanmamış kod repository’ye giremiyor.

pip install pre-commit
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black
        language_version: python3.11

  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black-jupyter
        language_version: python3.11

  # isort ile birlikte kullanım (import sıralama)
  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort
        args: ["--profile", "black"]

  # flake8 ile lint kontrolü
  - repo: https://github.com/pycqa/flake8
    rev: 7.0.0
    hooks:
      - id: flake8
        args: ["--max-line-length=88", "--extend-ignore=E203,W503"]
# Hook'ları kur
pre-commit install

# Tüm dosyalar üzerinde manuel çalıştır
pre-commit run --all-files

# Sadece Black'i çalıştır
pre-commit run black --all-files

isort’u Black ile kullanırken --profile black argümanını vermek kritik. Aksi hâlde ikisi çatışıyor ve commit her seferinde başarısız oluyor. Bunu öğrenmek bana bir saat kaybettirdi, siz öğrenmeden geçin.

CI/CD Pipeline Entegrasyonu

Lokal pre-commit hook’ları geliştiricilerin kendi makinelerinde çalışıyor. Ama ya birisi hook’ları kurmadıysa? Ya da direkt push yaptıysa? CI/CD’de ikinci bir güvenlik katmanı şart.

GitHub Actions için:

# .github/workflows/code-quality.yml
name: Code Quality

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  black-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Python kur
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Bağımlılıkları yükle
        run: |
          pip install black==23.12.1

      - name: Black format kontrolü
        run: |
          black --check --diff .

  lint-check:
    runs-on: ubuntu-latest
    needs: black-check
    steps:
      - uses: actions/checkout@v4

      - name: Python kur
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Araçları yükle
        run: |
          pip install flake8 isort mypy

      - name: isort kontrolü
        run: isort --check-only --profile black .

      - name: flake8 kontrolü
        run: flake8 . --max-line-length=88 --extend-ignore=E203,W503

      - name: mypy tip kontrolü
        run: mypy src/ --ignore-missing-imports

GitLab CI için de benzer yapı:

# .gitlab-ci.yml
stages:
  - quality

black-format-check:
  stage: quality
  image: python:3.11-slim
  before_script:
    - pip install black==23.12.1 isort flake8
  script:
    - black --check --diff .
    - isort --check-only --profile black .
    - flake8 . --max-line-length=88 --extend-ignore=E203,W503
  only:
    - merge_requests
    - main
    - develop

Gerçek Dünya Senaryosu: Eski Projeyi Black’e Taşımak

Bir Django projesini Black’e geçirme sürecinde yaşananları aktarayım. Proje üç yıllık, 40.000 satır kod, beş farklı geliştirici katkısı ve sıfır format standardı. Black’i direkt black . ile çalıştırdığınızda git history’niz mahvoluyor çünkü her satır değişmiş gibi görünüyor.

Doğru yaklaşım şu:

# Önce ne kadar dosya etkilenecek görelim
black --check . 2>&1 | tail -5

# Büyük projelerde önce istatistik al
black --check . 2>&1 | grep "would reformat" | wc -l

# Formatlamayı yap ve commit mesajını özenle seç
black .
git add -A
git commit -m "style: apply black formatting to entire codebase

This commit applies Black code formatting uniformly.
Use 'git blame --ignore-rev' or add to .git-blame-ignore-revs
to exclude this commit from blame history."

# .git-blame-ignore-revs dosyası oluştur
echo "$(git rev-parse HEAD)" >> .git-blame-ignore-revs
git add .git-blame-ignore-revs
git commit -m "chore: add black formatting commit to blame ignore list"
# Git blame'i bu commit'i yok sayacak şekilde yapılandır
git config blame.ignoreRevsFile .git-blame-ignore-revs

Bu yaklaşım sayesinde git blame çalıştırdığınızda format commit’i değil, asıl kod değişikliklerini yapan kişileri görüyorsunuz. Ekipte bu konuşmayı yapmadan geçmeyin, aksi hâlde “kim bu kadar değişiklik yaptı?” soruları geliyor.

Black ile Flake8 Uyumu

Black ve flake8 bazen çatışıyor. Black’in ürettiği bazı kod stilleri flake8 kurallarını ihlal ediyor. En sık karşılaşılan çatışmalar için flake8 yapılandırması:

# setup.cfg veya .flake8
[flake8]
max-line-length = 88
extend-ignore =
    # Black ile çakışan kurallar
    E203,  # whitespace before ':'
    E501,  # line too long (black hallediyor)
    W503,  # line break before binary operator
    W504,  # line break after binary operator
exclude =
    .git,
    __pycache__,
    .venv,
    venv,
    build,
    dist,
    migrations,
    *.egg-info

Bu yapılandırmayla Black ve flake8 barış içinde çalışıyor.

Makefile ile Geliştirici Deneyimi

Tüm bu araçları bir Makefile altında toplamak ekip içi tutarlılığı artırıyor. Yeni bir geliştirici projeye katıldığında make help yazıyor ve ne yapacağını anlıyor:

# Makefile
.PHONY: help format lint type-check test all-checks

help:
	@echo "Kullanılabilir komutlar:"
	@echo "  make format      - Black ile kodu formatla"
	@echo "  make lint        - flake8 ve isort kontrol"
	@echo "  make type-check  - mypy tip kontrolü"
	@echo "  make all-checks  - Tüm kontrolleri çalıştır"

format:
	black .
	isort --profile black .

lint:
	black --check --diff .
	isort --check-only --profile black .
	flake8 . --max-line-length=88 --extend-ignore=E203,W503

type-check:
	mypy src/ --ignore-missing-imports --strict

all-checks: lint type-check
	@echo "Tüm kalite kontrolleri tamamlandı."

install-dev:
	pip install black isort flake8 mypy pre-commit
	pre-commit install
	@echo "Geliştirme ortamı hazır."

VSCode ve PyCharm Entegrasyonu

Editör entegrasyonu olmadan bu araçlar tam potansiyelini gösteremiyor. Kaydet tuşuna bastığınızda otomatik formatlanması iş akışını dramatik biçimde hızlandırıyor.

VSCode için .vscode/settings.json:

{
  "python.formatting.provider": "black",
  "editor.formatOnSave": true,
  "editor.formatOnSaveMode": "file",
  "[python]": {
    "editor.defaultFormatter": "ms-python.black-formatter",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  },
  "isort.args": ["--profile", "black"],
  "python.linting.flake8Enabled": true,
  "python.linting.flake8Args": [
    "--max-line-length=88",
    "--extend-ignore=E203,W503"
  ]
}

Bu dosyayı repository’ye commit edin. Ekipteki herkes aynı editör davranışını alıyor. “Ama ben PyCharm kullanıyorum” diyenler için: PyCharm’da Settings > Tools > External Tools altına Black’i ekleyebilir, klavye kısayolu atayabilirsiniz. PyCharm 2023.1 sonrasında ise doğrudan Black desteği var, Settings > Editor > Code Style > Python > Black formatter yolu ile etkinleştiriliyor.

SonarQube ile Entegrasyon

SonarQube kullanan ekipler için Black’i kalite kapısına dahil etmek mümkün. Doğrudan SonarQube Python analizörü PEP 8 kurallarını kontrol ediyor ama Black’i ayrı bir adım olarak çalıştırmak daha güvenilir bir yaklaşım.

# sonar-project.properties
sonar.projectKey=my-python-project
sonar.sources=src
sonar.tests=tests
sonar.python.version=3.11
sonar.python.flake8.reportPaths=flake8-report.txt

# Quality Gate'e özel kural eklemek için
sonar.issue.ignore.multicriteria=e1
sonar.issue.ignore.multicriteria.e1.ruleKey=python:S1192
sonar.issue.ignore.multicriteria.e1.resourceKey=**/migrations/**
# CI'da flake8 raporunu SonarQube formatında üret
flake8 . --max-line-length=88 
         --extend-ignore=E203,W503 
         --format='%(path)s:%(row)d:%(col)d: %(code)s %(text)s' 
         --output-file=flake8-report.txt || true

# Ardından sonar-scanner çalıştır
sonar-scanner 
  -Dsonar.projectKey=my-python-project 
  -Dsonar.host.url=http://sonarqube.sirket.local 
  -Dsonar.login=$SONAR_TOKEN

Ekip Standartlarını Belgeleme

Teknik araçlar kurulumun yarısı. Diğer yarısı insanların bu araçları neden kullandığını anlaması. Her projeye CONTRIBUTING.md ekleyin:

## Kod Stili

Bu proje kod formatlaması için **Black** kullanıyor.

### Kurulum

```bash
make install-dev
```

Bu komut Black, isort, flake8 ve pre-commit hook'larını kurar.

### Kod Gönderme Öncesi

Pull request açmadan önce:

```bash
make format    # Kodu formatla
make lint      # Lint kontrolü yap
```

### Neden Black?

Code review'larda format tartışmaları yaşamamak için. Black'in kararlarını
beğenmeyebilirsiniz, ekip de beğenmeyebilir. Ama tutarlılık, tercihten
daha değerlidir.

Bu belge kısa, net ve gerekçeli. Uzun stil kılavuzları okunmuyor, bunun yerine araçların kendi kararlarına bırakmak daha etkili.

Performans ve Büyük Projelerde Dikkat Edilecekler

Büyük projelerde Black’in her çalışmada tüm dosyaları işlemesi zaman alabilir. Black cache mekanizması varsayılan olarak açık, ancak CI’da cache’i korumak biraz yapılandırma gerektiriyor:

# Black cache konumunu öğren
black --version

# Cache dizini genellikle burada
ls ~/.cache/black/

# CI'da cache'i GitHub Actions ile koru
- name: Black cache
  uses: actions/cache@v3
  with:
    path: ~/.cache/black
    key: black-${{ runner.os }}-${{ hashFiles('pyproject.toml') }}

Çok büyük monorepolarda Black’i sadece değişen dosyalar üzerinde çalıştırmak daha mantıklı:

# Sadece main'e göre değişen Python dosyalarını formatla
git diff --name-only main HEAD | grep '.py$' | xargs black

Sonuç

Black’i bir projeye entegre etmek başlangıçta sürtüşme yaratabilir. “Neden benim tercihlerim yok sayılıyor?” sorusu kaçınılmaz olarak geliyor. Cevap şu: çünkü kod stili tartışmaları için harcanan zaman, asıl işe, yani problemi çözmeye harcanabilir.

Pratikte yaşadığım en somut fayda şu oldu: Code review’larda “neden burada virgül var?” yerine “bu algoritma doğru mu?” konuşuyoruz. Bu küçük gibi görünüyor ama ekip olarak odağın teknik içeriğe kayması üretkenliği ciddi ölçüde artırıyor.

Araç zincirini özetlemek gerekirse: Black format için, isort import sıralaması için, flake8 lint için, mypy tip kontrolü için. Bunların tamamını pre-commit hook ve CI/CD pipeline’a bağlayın. Makefile ile tek komuta indirgeyin. VSCode veya PyCharm entegrasyonunu repository’ye commit edin.

Eğer sıfırdan başlıyorsanız önce Black ve pre-commit hook entegrasyonunu kurun. Gerisi gelebilir. Ama var olan bir projeyi taşıyorsanız, git-blame-ignore-revs adımını atlamayın. Ekipte bu format commit’ini kimin yazdığını soran insanlar çıkacak, önceden hazırlıklı olun.

Kod kalitesi tek bir araçla gelmiyor, bir kültür meselesi. Black bu kültürü otomatize etmenin en iyi başlangıç noktalarından biri.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir