Ana içeriğe geç
Kesit Python
Temel · Dosya ve hata

Hata ayıklama (debugging)

Traceback'i alttan yukarı okur, print ve f"{x=}" ile izler, assert ile varsayımları denetler, pdb ve VS Code ile adım adım ilerler, sorunu küçültüp doğru soruyu sorarak hatayı yöntemle bulursun.

Uygulamada aç, 20 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden önemli?

Anadolu Market'in gece çalışan ciro raporu bu sabah İzmir için 0 TL gösterdi. Program hata vermedi, sadece yanlış sonuç üretti. Ekipte biri "dün çalışıyordu" dedi, bir başkası kodu baştan yazmayı önerdi. Deneyimli geliştirici ise şunu yaptı: hatayı tekrar üretti, veriyi küçülttü, iki print ekledi ve on dakikada sorunun bir sözlük anahtarındaki tek harf olduğunu buldu. Hata ayıklama (debugging), yazılımcının günlük işinin belki de yarısıdır ve şansa değil yönteme dayanır. Bu derste o yöntemi öğreneceksin.

try/except ile hatayı yakalamayı ve kendi hatanı fırlatmayı "Hatalar ve istisnalar" dersinde gördün. Burada amaç farklı: hatanın nedenini bulmak ve düzeltmek.

Üç hata ailesi

AileNe zaman ortaya çıkar?ÖrnekBelirti
Sözdizimi hatası (syntax error)Kod daha çalışmadan, derlenirkenKapanmamış parantez, eksik iki nokta, yanlış girintiProgram hiç başlamaz; SyntaxError, IndentationError
Çalışma zamanı hatası (runtime error, exception)Kod çalışırken, o satıra gelinceSıfıra bölme, olmayan anahtar, yanlış türTraceback ile durur: ZeroDivisionError, KeyError, TypeError
Mantık hatası (logic error)Hiçbir zaman "patlamaz"Yanlış aralık, yanlış koşul, yanlış formülProgram sessizce yanlış sonuç verir

En tehlikelisi sonuncusudur: kimse fark etmezse yanlış rakam yönetim raporuna kadar gider. Sözdizimi ve çalışma zamanı hataları ise aslında dosttur; sana tam olarak nerede durduklarını söylerler.

Traceback'i alttan yukarı oku

Python bir istisna fırlattığında geriye dönük iz (traceback) yazdırır. Yeni başlayanlar en üstten okumaya başlar ve kaybolur. Doğru sıra tam tersidir:

Traceback (most recent call last):
  File "gunluk_rapor.py", line 12, in <module>
    print(sehir_ozeti(siparisler))
  File "gunluk_rapor.py", line 7, in sehir_ozeti
    toplam[s["sehir"]] += s["tutar"]
KeyError: 'İzmir'
  1. En alt satır: hata türü ve mesajı. KeyError: 'İzmir' → sözlükte "İzmir" anahtarı yok.
  2. Hemen üstündeki File satırı: hatanın gerçekleştiği yer. 7. satır, sehir_ozeti fonksiyonu.
  3. Kod satırı: toplam[s["sehir"]] += s["tutar"]. += önce okur; anahtar ilk kez geliyorsa okuma başarısız olur.
  4. Yukarı çık: bu fonksiyonu kim çağırdı? 12. satır. Üst satırlar "buraya nasıl gelindi" hikâyesidir.

"most recent call last" ifadesi zaten ipucunu verir: en son çağrı en alttadır. Kütüphane kodunun içinde biten bir traceback'te, alttan yukarı çıkarken kendi dosyanın ilk satırını ara; hata çoğunlukla oraya senin verdiğin bir değerden kaynaklanır.

siparisler = [
    {"sehir": "İzmir", "tutar": 300},
    {"sehir": "Ankara", "tutar": 150},
    {"sehir": "İzmir", "tutar": 120},
]

def sehir_ozeti(siparisler):
    toplam = {}
    for s in siparisler:
        # Hatalı hâli: toplam[s["sehir"]] += s["tutar"]
        toplam[s["sehir"]] = toplam.get(s["sehir"], 0) + s["tutar"]
    return toplam

print(sehir_ozeti(siparisler))

Hata türüne göre ilk bakılacak yer

HataGenelde ne demek?İlk bakacağın yer
SyntaxErrorPython cümleyi anlayamadıGösterilen satır ve bir üstü: kapanmamış parantez/tırnak, eksik :
IndentationErrorGirinti tutarsızSekme/boşluk karışımı, blok sonrası boş gövde
NameErrorBu ad tanımlı değilYazım hatası, tanımdan önce kullanım, yanlış kapsam
TypeErrorBu türle bu işlem olmazinput()'tan gelen metin, None dönen fonksiyon, eksik argüman
ValueErrorTür doğru, değer uygunsuzint("12,5") gibi dönüşümler, veri temizliği
KeyErrorSözlükte bu anahtar yokAnahtarın yazımı, büyük-küçük harf, Türkçe karakter
IndexErrorListe bu kadar uzun değilrange sınırları, boş liste, off-by-one
AttributeErrorBu nesnede bu nitelik yokDeğişken beklediğin türde değil (çoğu zaman None)
ZeroDivisionErrorSıfıra bölmeBoş listede ortalama, sıfır adet

print ile izleme ve f"{x=}" kalıbı

En eski ve hâlâ en hızlı araç: şüphelendiğin yere değerleri yazdırmak. Python 3.8'den beri f-string içinde = kullanırsan hem ifadeyi hem değerini yazar; etiketi elle yazma derdi biter.

def kargo_ucreti(agirlik_kg, sehir):
    taban = 40
    ek = (agirlik_kg - 1) * 12 if agirlik_kg > 1 else 0
    uzak = sehir in ("Van", "Hakkari")
    print(f"{agirlik_kg=} {ek=} {uzak=}")      # geçici izleme satırı
    ucret = taban + ek + (25 if uzak else 0)
    print(f"{ucret=}  {ucret / agirlik_kg=:.2f}")
    return ucret

kargo_ucreti(3, "Van")
kargo_ucreti(0.5, "İzmir")
İzleme satırını işaretleGeçici print satırlarının sonuna # DEBUG yaz; iş bitince hepsini tek aramayla bul ve sil. Kalıcı izleme gerekiyorsa print değil logging kullanılır (orta seviyede göreceksin).

assert ile varsayım kontrolü

Her kodun içinde söylenmemiş varsayımlar vardır: "liste boş olmaz", "indirim oranı 0 ile 1 arasındadır". assert koşul, "mesaj" bu varsayımı yazıya döker; koşul yanlışsa AssertionError fırlatır. Böylece hata, yanlış değerin doğduğu yerde yakalanır, üç fonksiyon sonra değil.

def indirim_uygula(fiyat, oran):
    assert 0 <= oran <= 1, f"oran 0-1 arasında olmalı, gelen: {oran}"
    yeni = fiyat * (1 - oran)
    assert yeni <= fiyat, "indirimli fiyat eskisinden büyük olamaz"
    return round(yeni, 2)

print(indirim_uygula(200, 0.15))
try:
    print(indirim_uygula(200, 15))      # yüzde yerine 15 verildi
except AssertionError as hata:
    print("Yakalandı:", hata)
assert kullanıcı girdisi için değildirPython -O seçeneğiyle çalıştırılınca tüm assert satırları atlanır. Kullanıcıdan ya da dosyadan gelen veriyi doğrulamak için if + raise ValueError kullan; assert yalnızca "bu asla olmamalı" dediğin programcı varsayımları içindir.

breakpoint() ve pdb

print'in yetmediği yerde hata ayıklayıcı (debugger) devreye girer: programı bir satırda durdurur, değişkenlere bakmanı ve satır satır ilerlemeni sağlar. Kodun içine breakpoint() yazman yeterli; Python yerleşik pdb ayıklayıcısını açar.

def faiz_hesapla(anapara, yillik_oran, ay):
    aylik = yillik_oran / 12
    breakpoint()                 # program burada durur, (Pdb) istemi açılır
    return anapara * (1 + aylik) ** ay

faiz_hesapla(10_000, 0.42, 12)
KomutAçılımıNe yapar?
nnextSonraki satıra geç; fonksiyon çağrısının içine girmez
sstepSatırda bir fonksiyon çağrısı varsa içine gir
ccontinueBir sonraki durma noktasına kadar çalış
p ifadeprintDeğeri göster: p aylik, p anapara * 2
llistBulunduğun yerin çevresindeki kodu göster
wwhereÇağrı yığınını göster (canlı traceback)
qquitAyıklayıcıdan ve programdan çık
Kendi bilgisayarında deneTarayıcıdaki çalıştırıcı etkileşimli konsol açmadığı için breakpoint()'i burada değil, terminalde dene: python faiz.py. Program çökmüşse python -m pdb faiz.py ile başlatıp hata anında otomatik durdurabilirsin (post-mortem).

VS Code hata ayıklayıcısı

Aynı işi fareyle yapmak istersen VS Code'un Python eklentisi yeterli:

  1. Satır numarasının soluna tıkla (ya da F9): kırmızı nokta, yani kesme noktası (breakpoint).
  2. F5 → "Python File". Program kırmızı noktada durur.
  3. Sol panelde Variables tüm yerel değişkenleri gösterir; Watch'a ifade ekleyip izleyebilirsin.
  4. F10 step over (n), F11 step into (s), Shift+F11 fonksiyondan çık, F5 devam (c).
  5. Kesme noktasına sağ tıklayıp koşullu yapabilirsin: s["sehir"] == "İzmir". Bin kayıtlık döngüde yalnızca ilgilendiğin kayıtta durur.
  6. Debug Console'da durduğun andaki değişkenlerle Python ifadesi çalıştırabilirsin.

Sorunu küçült: minimal örnek

Elinde 50 bin satırlık sipariş dosyası ve bir yerde patlayan bir rapor var. Gözle aramak yerine sorunu üreten en küçük girdiyi bul. Önce hatayı üreten kaydı otomatik yakala, sonra yalnızca o kayıtla çalış:

kayitlar = [
    {"no": "AM-1", "tutar": "120.5"},
    {"no": "AM-2", "tutar": "89"},
    {"no": "AM-3", "tutar": "1.250,00"},     # Türkçe biçim: float'a çevrilmez
    {"no": "AM-4", "tutar": "40"},
]

for i, k in enumerate(kayitlar):
    try:
        float(k["tutar"])
    except ValueError as hata:
        print(f"{i=} {k['no']=} -> {hata}")
        break

Artık sorun "rapor çalışmıyor" değil, "float("1.250,00") ValueError veriyor". Bu cümleyle düzeltme yapmak, aramak ve soru sormak çok kolaydır. Minimal örneğin kuralları: hatayı hâlâ üretsin, bunun dışında her şey silinsin, başkası kopyalayıp çalıştırabilsin.

Rubber duck: sesli anlat

Masaya bir lastik ördek koy ve kodunu ona satır satır anlat: "Burada listeyi geziyorum, burada toplamı artırıyorum, burada döndürüyorum..." Çoğu zaman cümlenin ortasında "dur, return döngünün içinde" dersin. Yöntemin adı komik ama işe yarar, çünkü sesli anlatmak seni varsaydığın şeyi değil, kodun gerçekten yaptığını okumaya zorlar.

Hata mesajını aramak ve yapay zekâya sormak

Hata mesajının genel kısmını ara, sana özel kısmını çıkar: "TypeError: unsupported operand type(s) for +: 'int' and 'str'" iyi bir aramadır; dosya adların ve değişken adların değildir. Yapay zekâya ya da bir foruma sorarken şu beşini ver:

  1. Ne yapmaya çalışıyorsun? (bir cümle iş hedefi)
  2. Minimal kod (çalıştırılabilir, gizli bilgi ve müşteri verisi içermeyen)
  3. Tam hata mesajı (traceback'in tamamı, ekran görüntüsü değil metin)
  4. Beklenen ve gelen sonuç
  5. Denediklerin ve Python sürümün
Yapıştırmadan önce düşünŞirket kodu, müşteri adları, IBAN, şifre ve API anahtarları dış bir hizmete gönderilmez. Minimal örnek hazırlamanın bir faydası da budur: gerçek veriyi kurgusal küçük veriyle değiştirirsin. Gelen cevabı da körü körüne yapıştırma; neden çalıştığını anlamadığın düzeltme bir sonraki hatanın tohumudur.

Klasik böcekler kataloğu

BöcekBelirtiDüzeltme
Off-by-oneSon ya da ilk eleman eksikrange bitişinin hariç olduğunu hatırla
Döngü içinde returnYalnızca ilk eleman işlenirreturn'ü döngü hizasından çıkar
== yerine = ya da tersiDeğer güncellenmezAtama =, karşılaştırma ==
Tamsayı bölmeKuruşlar kaybolur// yerine /
Değişebilir varsayılanÇağrılar arasında veri sızarNone varsayılanı + içeride yeni liste
Gölgeleme (shadowing)'int' object is not callablesum, list, max adlarını değişkene verme
Yanlış anahtarKeyError ya da .get ile sessiz 0Anahtarı veriden kopyala
Gerçek işteProfesyonel ekiplerde bir hata bildirimi (bug report) "çalışmıyor" diye açılmaz: adımlar, beklenen, gelen, ortam ve log eklenir. Düzeltmeden önce hatayı yeniden üreten bir test yazılır; test kırmızıyken düzeltilir, yeşile dönünce kod incelemesine (code review) gönderilir. Böylece aynı hata bir daha geri gelirse test hemen yakalar. Mülakatlarda da sık sorulur: "Son zamanlarda ayıkladığın zor bir hatayı anlat." İyi cevap, yöntemi anlatan cevaptır.
Uzmanlaş

Bilimsel yöntem olarak hata ayıklama

Gözlem → hipotez → deney → sonuç. "Sanırım tutar metin olarak geliyor" bir hipotezdir; print(f"{type(tutar)=}") onun deneyidir. Hipotezleri tek tek ele, aynı anda üç şeyi değiştirme; yoksa hangisinin düzelttiğini bilemezsin.

İkiye bölerek arama

Kod dün çalışıyordu, bugün çalışmıyorsa aradaki değişikliklerin ortasını dene, sonra kalan yarının ortasını... Git bunu git bisect komutuyla otomatik yapar: yüz commit arasından bozan commit'i yaklaşık yedi denemede bulursun. Aynı fikir veri için de geçerlidir: dosyanın yarısıyla dene, hata hangi yarıdaysa onu böl.

Heisenbug ve ortam farkları

Bazı hatalar gözlemlediğinde kaybolur (zamanlama, rastgelelik, önbellek (cache)). Rastgelelikte random.seed ile tekrarlanabilir hâle getir; ortam farkında python --version, pip freeze ve işletim sistemini karşılaştır. "Bende çalışıyor" cümlesi bir sonuç değil, bir ipucudur: iki ortam arasında bir fark var demektir.

Post-mortem

Uzun süren bir iş gecenin üçünde çöktüyse, interaktif konsolda import pdb; pdb.pm() son istisnanın yığınına girip o anki değişkenleri gösterir. Üretimde ise log ve hata izleme servisleri (Sentry gibi) traceback'i değişken değerleriyle birlikte saklar.

Alıştırmalara geç (20)