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.
