Linting ve Kod Kalitesi Araçları
Hiç başkasının yazdığı kodu açıp "bu ne böyle?" dediğin oldu mu? Değişken isimleri anlamsız, import'lar dağınık, bazen boşluk var bazen yok, satır uzunlukları uçmuş... Kod çalışıyor ama okumak işkence. Şimdi bir de tersten düşün: sen yazıyorsun, altı ay sonra açıyorsun ve kendi kodunu tanıyamıyorsun.
İşte linter ve formatter araçları tam bu sorunu çözer. Kodu standartlara otomatik uyduran, potansiyel hataları daha çalıştırmadan yakalayan ve takımdaki herkesin aynı stilde yazmasını sağlayan araçlar. Bu derste Python ekosisteminin en önemli kod kalitesi araçlarını — flake8, black, isort, ruff, mypy — öğrenecek ve bunları pre-commit hook'ları ile CI/CD pipeline'ına entegre edeceksin.
1. Neden Linter Kullanırız?
🏭 Analoji: Fabrikanın Kalite Kontrol Bandı
Bir fabrikayı düşün. Üretim hattının sonunda bir kalite kontrol bandı var. Her ürün bu banttan geçer — boyutu ölçülür, kusurları taranır, standartlara uygunluğu kontrol edilir. Hatalı ürünler yakalanır, geri gönderilir. Bu bant olmasa defolu ürünler müşteriye ulaşır.
Linter da kodun kalite kontrol bandıdır. Her commit'ten önce kodun bu banttan geçer — stil hataları yakalanır, potansiyel bug'lar işaretlenir, standartlara uyum kontrol edilir. Sorun production'a ulaşmadan düzeltilir.
Linter Ne Yapar?
Linter'lar iki temel iş yapar:
Stil kontrolü: Boşluklar, satır uzunlukları, isimlendirme kuralları (PEP 8 uyumu)
Hata tespiti: Kullanılmayan import'lar, tanımsız değişkenler, ulaşılamaz kod, tip hataları
Bunları gözle yakalamak hem zor hem de zaman kaybı. Bir linter bunu milisaniyeler içinde yapar.
Linter Olmadan Ne Olur?
# ❌ Her geliştirici kendi stilinde yazıyor
import os,sys
import json
from pathlib import Path
import requests
import os # tekrar import!
def hesapla_toplam(liste,vergi_orani):
toplam=0
for i in liste:
toplam+=i
if vergi_orani>0:
return toplam*(1+vergi_orani)
else :
return toplam
class musteri_bilgisi:
def __init__(self,ad,soyad):
self.Ad=ad
self.Soyad=soyadBu kodda en az 10 stil sorunu var: boşluk eksiklikleri, tekrar eden import, PEP 8'e aykırı isimlendirme, gereksiz else, tutarsız attribute isimleri... Kod çalışır ama okunması ve bakımı kabus.
2. flake8: Klasik Linter
flake8, Python'un en yaygın kullanılan linter'ıdır. PyFlakes (mantık hataları), pycodestyle (PEP 8 stil kontrolü) ve McCabe (karmaşıklık analizi) araçlarını bir arada sunar.
Kurulum ve Temel Kullanım
pip install flake8
# Tek dosya kontrol et
flake8 app.py
# Tüm proje
flake8 src/
# Belirli hataları göster
flake8 --select=E,W src/ # Sadece error ve warning
# Belirli hataları yoksay
flake8 --ignore=E501,W503 src/ # Satır uzunluğu ve satır sonu kuralını atlaYaygın Uyarı Kodları
flake8 her uyarıyı bir kodla raporlar. En sık karşılaşacakların:
| Kod | Açıklama | Örnek |
|---|---|---|
| E501 | Satır 79 karakterden uzun | x = very_long_variable_name + another_long_... |
| E302 | Fonksiyon/class önünde 2 boş satır olmalı | Fonksiyonlar arası tek boş satır bırakmak |
| E303 | Fazla boş satır | 3+ ardışık boş satır |
| W291 | Satır sonunda gereksiz boşluk | x = 5 (sondaki boşluklar) |
| F401 | Import edilmiş ama kullanılmamış modül | import os ama os hiç kullanılmıyor |
| F841 | Atanmış ama kullanılmamış değişken | x = 5 ama x hiç kullanılmıyor |
| E711 | == None yerine is None kullan | if x == None: |
| E712 | == True yerine direkt kullan | if x == True: → if x: |
Konfigürasyon
Her seferinde komut satırında parametre geçmek yerine proje kökünde bir konfigürasyon dosyası oluştur:
# .flake8 dosyası (proje kökünde)
[flake8]
max-line-length = 120
max-complexity = 10
exclude =
.git,
__pycache__,
venv,
migrations,
.eggs
ignore =
W503,
E203
per-file-ignores =
__init__.py: F401
tests/*: E501Önemli ayarlar:
max-line-length: Varsayılan 79 çoğu takım için dar. 120 yaygın bir tercih.max-complexity: McCabe karmaşıklık skoru — fonksiyondaki dallanma sayısını ölçer.exclude: Kontrol edilmeyecek dizinler.per-file-ignores: Dosya bazında istisnalar.__init__.py'da kullanılmayan import'lar normal (modül export için), test dosyalarında uzun satırlar da kabul edilebilir.
Alternatif olarak setup.cfg veya tox.ini dosyasında da aynı konfigürasyonu [flake8] bölümü altına yazabilirsin.
3. black: Opinionated Formatter
flake8 sorunları gösterir ama düzeltmez. black ise kodu alır, kendi stiline göre otomatik düzeltir. "Opinionated" (fikirli) olmasının sebebi, tartışmaya yer bırakmaması — black'in bir stili var ve herkes onu kullanır.
Felsefesi
black'in mottosu: "Any color you like, as long as it's black." Stil tartışmaları takımların en verimli zamanını çalar. black bu tartışayı tamamen ortadan kaldırır: herkes aynı formatı kullanır, nokta.
Kurulum ve Kullanım
pip install black
# Tek dosyayı formatla
black app.py
# Tüm projeyi formatla
black src/
# Önizleme — değişiklikleri göster, uygulamadan
black --diff app.py
# Kontrol modu — değişiklik gerekiyorsa hata kodu döner (CI için)
black --check src/Önce ve Sonra
# ❌ ÖNCE — dağınık formatlama
x = { 'a':37,'b':42,
'c':927}
y = 'hello ''world'
z = 'hello '+'world'
if very_long_variable_name is not None and \
another_long_variable is not None and \
yet_another is not None:
do_something()
def f(a,b,c=5,d={'key':'value'}):
return a+b*c# ✅ SONRA — black'in formatladığı hali
x = {"a": 37, "b": 42, "c": 927}
y = "hello " "world"
z = "hello " + "world"
if (
very_long_variable_name is not None
and another_long_variable is not None
and yet_another is not None
):
do_something()
def f(a, b, c=5, d={"key": "value"}):
return a + b * cblack'in yaptığı başlıca değişiklikler:
Tek tırnak → çift tırnak (tutarlılık)
Operatörler etrafına boşluk
Uzun satırları akıllıca kırma
Fonksiyon/class arası standart boş satırlar
Trailing comma (son elemandan sonra virgül) stratejisi
Konfigürasyon
black kasıtlı olarak az ayar sunar. Konfigürasyon pyproject.toml dosyasında yapılır:
# pyproject.toml
[tool.black]
line-length = 120
target-version = ["py311"]
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.venv
| migrations
)/
'''black ile flake8'i birlikte kullanıyorsan satır uzunluğunu ikisinde de aynı tutmalısın. Ayrıca flake8'in E203 ve W503 uyarılarını kapatman gerekir — black'in formatlaması bu kurallarla çelişir.
4. isort: Import Düzenleyici
Python dosyalarının başındaki import satırları zamanla karmaşıklaşır. isort bunları otomatik sıralar ve gruplar.
PEP 8 Import Sıralaması
PEP 8'e göre import'lar şu sırada olmalı:
Standart kütüphane —
os,sys,jsonÜçüncü parti —
requests,pandas,flaskYerel modüller —
from myapp import utils
Her grup arasında bir boş satır.
Kurulum ve Kullanım
pip install isort
# Tek dosya
isort app.py
# Tüm proje
isort src/
# Kontrol modu (CI için)
isort --check-only --diff src/Önce ve Sonra
# ❌ ÖNCE — karışık import'lar
import json
from flask import Flask, jsonify
import os
from myapp.models import User
import requests
from pathlib import Path
import sys
from myapp.utils import helper
from datetime import datetime# ✅ SONRA — isort'un düzenlediği hali
import json
import os
import sys
from datetime import datetime
from pathlib import Path
import requests
from flask import Flask, jsonify
from myapp.models import User
from myapp.utils import helperblack ile Uyumluluk
isort ve black'in çıktıları çakışabilir. Bunu önlemek için isort'a black profili kullanmasını söyle:
# pyproject.toml
[tool.isort]
profile = "black"
line_length = 120
known_third_party = ["flask", "requests", "pandas"]
known_first_party = ["myapp"]profile = "black" ayarı isort'un black ile uyumlu formatta çıktı vermesini sağlar. Bu tek satır pek çok uyumsuzluk sorununu çözer.
5. ruff: Tek Araç, Hepsini Yönetir
ruff, Rust ile yazılmış yeni nesil bir Python linter ve formatter. flake8, isort, pyflakes, pycodestyle ve daha birçok aracın kurallarını tek çatı altında toplar — ve onlardan 10-100 kat daha hızlı çalışır.
Neden ruff?
Geleneksel araç seti şöyle görünür:
# Eski yöntem: 4 farklı araç
pip install flake8 black isort pyflakes
flake8 src/
black src/
isort src/ruff ile:
# Yeni yöntem: tek araç
pip install ruff
ruff check src/ # lint (flake8 + isort + pyflakes + ...)
ruff format src/ # format (black uyumlu)Kurulum ve Kullanım
pip install ruff
# Lint kontrolü
ruff check .
# Otomatik düzeltme
ruff check --fix .
# Formatlama (black uyumlu)
ruff format .
# Kontrol modu (CI için)
ruff check .
ruff format --check .Konfigürasyon
# pyproject.toml
[tool.ruff]
line-length = 120
target-version = "py311"
[tool.ruff.lint]
# Hangi kural setleri aktif?
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"SIM", # flake8-simplify
"RUF", # ruff-specific rules
]
ignore = ["E501"] # Satır uzunluğunu formatter'a bırak
[tool.ruff.lint.isort]
known-first-party = ["myapp"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"ruff'ın Hız Farkı
Büyük projelerde fark dramatiktir:
# 1000 dosyalık bir projede
$ time flake8 src/
real 0m4.200s
$ time ruff check src/
real 0m0.040s # ~100x daha hızlı!Bu hız farkı özellikle pre-commit hook'larında ve CI/CD pipeline'larında fark yaratır. Geliştiriciler her commit'te saniyeler beklemek yerine anında sonuç alır.
💡 İpucu: Yeni projelerde doğrudan ruff ile başla. Mevcut projelerde flake8 + black + isort üçlüsünden ruff'a geçiş genellikle sorunsuz — kural kodları büyük ölçüde aynı.
6. mypy: Statik Tip Kontrolü
Python dinamik tipli bir dil ama tip hatası (type hint) desteği var. mypy bu tip ipuçlarını analiz eder ve runtime'dan önce hataları yakalar.
Neden Tip Kontrolü?
# ❌ Bu hata runtime'da patlıyor
def hesapla_kdv(fiyat, oran):
return fiyat * (1 + oran)
# Yanlışlıkla string geçirildi — TypeError runtime'da!
sonuc = hesapla_kdv("100", 0.18)# ✅ Tip ipuçları ile — mypy derleme zamanında yakalar
def hesapla_kdv(fiyat: float, oran: float) -> float:
return fiyat * (1 + oran)
sonuc = hesapla_kdv("100", 0.18) # mypy: Argument 1 has incompatible type "str"Kurulum ve Kullanım
pip install mypy
# Tek dosya
mypy app.py
# Tüm proje
mypy src/
# Strict mode — en katı kontrol
mypy --strict src/Konfigürasyon
# pyproject.toml
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
no_implicit_optional = true
strict_equality = true
# Üçüncü parti kütüphaneler için tip stub'ları eksik olabilir
[[tool.mypy.overrides]]
module = ["requests.*", "pandas.*"]
ignore_missing_imports = trueYaygın Tip Örüntüleri
from typing import Optional
# Basit tip ipuçları
def selamla(isim: str) -> str:
return f"Merhaba, {isim}!"
# Optional — None olabilir
def kullanici_bul(id: int) -> Optional[dict]:
if id <= 0:
return None
return {"id": id, "isim": "Ali"}
# Liste ve dict tipleri
def ortalama_hesapla(sayilar: list[float]) -> float:
return sum(sayilar) / len(sayilar)
def config_oku(ayarlar: dict[str, str]) -> None:
for anahtar, deger in ayarlar.items():
print(f"{anahtar} = {deger}")Strict Mode
--strict flag'i şu kontrolleri aktifleştirir:
Tüm fonksiyonlar tip ipucu zorunlu
Anytipi uyarı verirImplicit
Optionalyasaklanır (NonedönüyorsanOptionalyazmalısın)Dekoratörsüz fonksiyonlar da kontrol edilir
Yeni projelerde strict mode ile başlamak en sağlıklısı. Mevcut projelere strict mode eklemek ise genellikle aşamalı yapılır — önce hata sayısını gör, sonra modül modül düzelt.
7. pre-commit Hooks
Tüm bu araçları "her commit öncesi otomatik çalıştırmak" istersen pre-commit framework'ünü kullanırsın. Git'in pre-commit hook mekanizması üzerine kurulu bir araç.
Kurulum
pip install pre-commitKonfigürasyon Dosyası
Proje kökünde .pre-commit-config.yaml dosyası oluştur:
# .pre-commit-config.yaml
repos:
# ruff — lint + format (flake8, isort, black yerine tek araç)
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
# mypy — tip kontrolü
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.14.1
hooks:
- id: mypy
additional_dependencies: [types-requests]
# Genel kontroller
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace # Satır sonu boşlukları
- id: end-of-file-fixer # Dosya sonu yeni satır
- id: check-yaml # YAML syntax
- id: check-json # JSON syntax
- id: check-added-large-files # Büyük dosya uyarısı
- id: debug-statements # print/pdb kalıntılarıEğer ruff yerine klasik araçları tercih ediyorsan:
# Alternatif: ayrı araçlarla
repos:
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
args: [--max-line-length=120]
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isortHook'ları Aktifleştirme
# Hook'ları kur (projeye ilk kez)
pre-commit install
# Tüm dosyalarda elle çalıştır (ilk kurulumda)
pre-commit run --all-files
# Tek bir hook çalıştır
pre-commit run ruff --all-files
# Hook'ları güncelle
pre-commit autoupdatepre-commit install komutundan sonra her git commit komutu otomatik olarak hook'ları çalıştırır. Hata varsa commit engellenir — düzelt ve tekrar dene.
Tipik İş Akışı
$ git add .
$ git commit -m "Yeni özellik eklendi"
ruff.....................................................Passed
ruff-format..............................................Passed
mypy.....................................................Passed
trailing-whitespace......................................Fixed
end-of-file-fixer.......................................Fixed
check-yaml...............................................Passed
# trailing-whitespace ve end-of-file-fixer dosyaları düzeltti
# Bu yüzden tekrar add + commit gerekiyor
$ git add .
$ git commit -m "Yeni özellik eklendi"
# Bu sefer tüm hook'lar Passed → commit başarılı⚠️ Dikkat: pre-commit hook'ları yerel makinede çalışır. Bir geliştirici hook'ları kurmazsa (
pre-commit installçalıştırmazsa) kontrol atlanır. Bu yüzden CI/CD'de de aynı kontrolleri çalıştırmak kritik — bir sonraki bölümde bunu ele alacağız.
8. CI/CD'de Lint: GitHub Actions
pre-commit yerel makinede çalışır ama son savunma hattı CI/CD pipeline'ıdır. Pull request açıldığında veya main branch'e push yapıldığında otomatik kontrol.
GitHub Actions Workflow
# .github/workflows/lint.yml
name: Lint & Type Check
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Python kur
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Bağımlılıkları kur
run: |
python -m pip install --upgrade pip
pip install ruff mypy
pip install -r requirements.txt
- name: Ruff lint kontrolü
run: ruff check .
- name: Ruff format kontrolü
run: ruff format --check .
- name: mypy tip kontrolü
run: mypy src/ --ignore-missing-importsBu workflow her PR'da ve main/develop push'unda çalışır. Herhangi bir adım başarısız olursa PR merge edilemez (branch protection rules ile birlikte).
pre-commit CI (Alternatif)
pre-commit'in kendi CI servisi de var. .pre-commit-config.yaml zaten projedeyse ekstra workflow yazmaya gerek kalmaz:
# .github/workflows/pre-commit.yml
name: pre-commit
on:
push:
branches: [main]
pull_request:
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: pre-commit/action@v3.0.1Bu daha kısa ve .pre-commit-config.yaml'deki tüm hook'ları otomatik çalıştırır.
9. Araç Karşılaştırma Tablosu
Hangi aracı ne zaman kullanacağını seçmek için:
| Özellik | flake8 | ruff | pylint | black | mypy |
|---|---|---|---|---|---|
| Kategori | Linter | Linter + Formatter | Linter | Formatter | Tip kontrolü |
| Hız | Orta | Çok hızlı (Rust) | Yavaş | Orta | Yavaş |
| Kural sayısı | ~100 | ~800+ | ~400+ | — | — |
| Otomatik düzeltme | ❌ | ✅ | ❌ | ✅ | ❌ |
| Formatlama | ❌ | ✅ | ❌ | ✅ | ❌ |
| Import sıralama | Plugin gerekli | ✅ (dahili) | ❌ | ❌ | ❌ |
| Tip kontrolü | ❌ | ❌ | Kısmi | ❌ | ✅ (tam) |
| Konfigürasyon | .flake8 / setup.cfg | pyproject.toml | .pylintrc | pyproject.toml | pyproject.toml |
| Öğrenme eğrisi | Düşük | Düşük | Yüksek | Çok düşük | Orta |
| Topluluk trendi | Stabil | Hızla yükseliyor | Stabil | Standart | Standart |
Önerilen Kombinasyonlar
Yeni proje (2024+):
ruff (lint + format + isort) + mypy (tip kontrolü)İki araç, tüm ihtiyaçları karşılar. Hızlı, modern, tek konfigürasyon dosyası.
Mevcut proje (geçiş döneminde):
flake8 (lint) + black (format) + isort (import) + mypy (tip kontrolü)Bu üçlü yıllardır endüstri standardı. ruff'a geçiş zamanla yapılabilir.
Minimal setup (küçük/kişisel proje):
ruff check + ruff formatmypy bile opsiyonel. Hızlı başla, ihtiyaç oldukça ekle.
pylint Notu
pylint bu tabloda var ama bilinçli olarak ayrı tutuyoruz. Çok kapsamlı bir araç — kod stili, hata tespiti, refactoring önerileri, hatta kod tekrarı (duplication) tespiti yapabilir. Ama aşırı "gürültülü"dür: varsayılan konfigürasyonla neredeyse her satıra bir uyarı verir. Ciddi projeler için faydalı ama başlangıç için flake8 veya ruff çok daha pragmatik.
10. Tüm Araçları Bir Arada Kullanma
Gerçek bir projede bu araçlar birlikte çalışır. İşte sıfırdan bir Python projesi kurarken önerilen adımlar:
Proje Konfigürasyonu
Tek bir pyproject.toml dosyasında her şeyi yönetebilirsin:
# pyproject.toml
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
# === ruff ===
[tool.ruff]
line-length = 120
target-version = "py311"
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "UP", "B", "SIM", "RUF"]
ignore = ["E501"]
[tool.ruff.lint.isort]
known-first-party = ["myproject"]
[tool.ruff.format]
quote-style = "double"
# === mypy ===
[tool.mypy]
python_version = "3.11"
warn_return_any = true
disallow_untyped_defs = true
[[tool.mypy.overrides]]
module = ["requests.*", "pandas.*"]
ignore_missing_imports = truePre-commit + CI/CD
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.14.1
hooks:
- id: mypy
additional_dependencies: [types-requests]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: debug-statementsGeliştirici Onboarding
Yeni bir geliştirici projeye katıldığında:
# 1. Repo'yu klonla
git clone https://github.com/team/myproject.git
cd myproject
# 2. Sanal ortam oluştur
python -m venv venv
source venv/bin/activate
# 3. Bağımlılıkları kur
pip install -r requirements.txt
pip install -r requirements-dev.txt # ruff, mypy, pre-commit
# 4. Pre-commit hook'larını kur
pre-commit install
# 5. İlk kontrol — mevcut durum
pre-commit run --all-filesBu adımlardan sonra geliştirici her commit'te otomatik kontrol alır. Takımdaki herkes aynı kurallarla çalışır, code review'da stil tartışması olmaz.
Özet
Linter'lar kodu çalıştırmadan hataları ve stil sorunlarını yakalar — kalite kontrol bandın.
flake8 klasik ve güvenilir bir linter; black tartışmasız bir formatter; isort import düzenleyicisi.
ruff bu üç aracın işini tek başına yapar, Rust ile yazıldığı için 10-100x daha hızlıdır — yeni projelerde ilk tercih.
mypy statik tip kontrolü sağlar; tip ipuçlarıyla runtime hatalarını derleme zamanında yakalar.
pre-commit hook'ları her commit öncesi otomatik kontrol çalıştırır; CI/CD pipeline'ı ise son savunma hattıdır.
Modern Python projesinde
ruff + mypy + pre-commit + GitHub Actionsdörtlüsü endüstri standardı haline gelmiştir.
AI Asistan
Sorularını yanıtlamaya hazır