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

Hatalar ve istisnalar

Program çökmesin, anlamlı mesaj versin: hata türleri, traceback okuma, try/except/else/finally, raise ile kendi hatanı fırlatma ve savunmacı kod.

Uygulamada aç, 22 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?

Ilgaz Bank'ın gece çalışan rapor betiği, bir satırda tutar yerine "yok" yazdığı için saat 03:00'te çöktü ve sabah hiç rapor yoktu. Oysa o satırı atlayıp loga yazsa, kalan binlerce satır işlenirdi. Hata (error) kaçınılmazdır; mesele programın hatayı nasıl karşıladığıdır. Python'da hatalar istisna (exception) adlı nesnelerle temsil edilir ve yakalanabilir.

Hata mesajı (traceback) okuma

def ortalama(liste):
    return sum(liste) / len(liste)

try:
    ortalama([])
except Exception as e:
    print(type(e).__name__, "->", e)

Bu kodu yakalamasaydık Python şöyle bir traceback basardı:

Traceback (most recent call last):
  File "rapor.py", line 5, in <module>
    ortalama([])
  File "rapor.py", line 2, in ortalama
    return sum(liste) / len(liste)
ZeroDivisionError: division by zero

Okuma sırası: en alt satır hatanın türü ve mesajıdır; hemen üstü hatanın oluştuğu satır; yukarı doğru çıktıkça oraya hangi çağrılarla gelindiğini görürsün. Önce en alta bak, sonra kendi dosyandaki ilk satırı bul.

Sık karşılaşılan hata türleri

HataNe zamanÖrnek
SyntaxErrorKod daha çalışmadan: yazım hatasıeksik iki nokta, kapanmayan parantez
NameErrorTanımsız adprint(toplm)
TypeErrorYanlış tür"5" + 3
ValueErrorTür doğru, değer uygunsuzint("abc")
KeyErrorSözlükte olmayan anahtarmusteri["telefon"]
IndexErrorListede olmayan indeksliste[10]
ZeroDivisionErrorSıfıra bölmetoplam / 0
FileNotFoundErrorDosya yokopen("yok.txt")
AttributeErrorNesnede olmayan öznitelik"abc".push("d")

try / except: hatayı yakala

Hata çıkabilecek kodu try bloğuna, hata olursa yapılacak işi except bloğuna yaz. Yakalayacağın hata türünü açıkça belirt; birden fazla tür için demet ya da ayrı except blokları kullan.

ham_tutarlar = ["900.0", "820.5", "yok", "930", "", "45,50"]
toplam = 0.0
hatali = []

for ham in ham_tutarlar:
    try:
        toplam += float(ham)
    except ValueError:
        hatali.append(ham)

print("Toplam:", toplam)
print("Atlanan değerler:", hatali)
musteri = {"ad": "Ayşe", "sehir": "Ankara"}
alanlar = ["ad", "telefon", "sehir"]

for alan in alanlar:
    try:
        print(alan, "=", musteri[alan])
    except KeyError as e:
        print("eksik alan:", e)

try:
    sonuc = 100 / int("0")
except (ZeroDivisionError, ValueError) as e:
    print("hesap yapılamadı:", type(e).__name__)
Çıplak except yazmaexcept: ya da except Exception: her şeyi yutar: yazım hatasını, yanlış değişken adını, kullanıcının Ctrl+C'sini bile. Hata gizlenir, program yanlış sonuçla sessizce devam eder. Hangi hatayı beklediğini biliyorsan onu yaz; bilmiyorsan muhtemelen yakalamamalısın.

else ve finally

def dosya_toplami(yol):
    try:
        f = open(yol, encoding="utf-8")
    except FileNotFoundError:
        print("dosya yok:", yol)
        return 0
    else:
        toplam = sum(float(s) for s in f if s.strip())
        print("okundu:", yol)
        return toplam
    finally:
        print("işlem tamamlandı:", yol)

with open("tutarlar.txt", "w", encoding="utf-8") as f:
    f.write("100\n250.5\n\n49.5\n")

print(dosya_toplami("tutarlar.txt"))
print(dosya_toplami("olmayan.txt"))

Sıra sabittir: try → except (bir ya da daha çok) → else → finally. Gerçek kodda dosya için with kullanırsın; finally'nin yaptığı kapatma işini with zaten yapar.

raise: kendi hatanı fırlat

Fonksiyonun geçersiz girdi aldığında sessizce yanlış sonuç üretmesi, hata vermesinden kötüdür. raise ile uygun bir istisna fırlat; mesajı açıklayıcı yaz. Girdiyi fonksiyonun başında kontrol etmeye savunmacı kod (defensive programming) denir.

def para_cek(bakiye, tutar):
    if tutar <= 0:
        raise ValueError(f"tutar pozitif olmalı, gelen: {tutar}")
    if tutar > bakiye:
        raise ValueError(f"yetersiz bakiye: {bakiye} < {tutar}")
    return bakiye - tutar

print(para_cek(1000, 300))
for deneme in (-5, 5000):
    try:
        para_cek(1000, deneme)
    except ValueError as e:
        print("RED:", e)

Hangi tür? Değer uygunsuzsa ValueError, tür yanlışsa TypeError, bir şey bulunamadıysa KeyError/LookupError. Orta seviyede kendi hata sınıflarını (class YetersizBakiye(Exception)) tanımlayacaksın.

Yakala ya da bırakBir hatayı yalnızca onunla ilgili anlamlı bir şey yapabiliyorsan yakala: atla, varsayılan kullan, kullanıcıya düzgün mesaj ver. Yapamıyorsan bırak yukarı çıksın; çağıran fonksiyon ya da ana program karar versin. except bloğunda yalnızca raise yazarak hatayı loglayıp yeniden fırlatabilirsin.

Savunmacı kod: EAFP ve LBYL

İki yaklaşım var. LBYL (look before you leap): önce kontrol et, sonra yap: if "tutar" in kayit:. EAFP (easier to ask forgiveness than permission): yap, hata çıkarsa yakala: try: kayit["tutar"] except KeyError:. Python topluluğu çoğunlukla EAFP'yi tercih eder; özellikle kontrol ile kullanım arasında durum değişebiliyorsa (dosya, ağ). Ama girdinin geçerliliğini fonksiyon başında raise ile doğrulamak her zaman iyidir.

kayit = {"id": 1001, "tutar": "900.0"}

# LBYL
if "tutar" in kayit and kayit["tutar"].replace(".", "").isdigit():
    print("LBYL:", float(kayit["tutar"]))

# EAFP
try:
    print("EAFP:", float(kayit["tutar"]))
except (KeyError, ValueError) as e:
    print("geçersiz kayıt:", e)

# dict.get ile varsayılan: en kısa yol
print(kayit.get("sehir", "bilinmiyor"))
Derinleş

Derinleş: istisnaların aile ağacı ve sağlam hata akışı

try/except/else/finally ve raise'ı öğrendin. Şimdi PCEP/PCAP sınavlarının ve iş görüşmelerinin sevdiği, gerçek projelerde de sık hataya yol açan ayrıntılara bakıyoruz: istisnaların hiyerarşisi (hierarchy), except bloklarının sırası, istisna nesnesinin içi, yeniden fırlatma ve "tekrar dene" kalıbı. Kendi istisna sınıf ağacını kurmak, raise ... from ve logging ayrıntıları orta seviyedeki Hata tasarımı dersinde; traceback okuma teknikleri ise Hata ayıklama (debugging) dersinde.

İstisnalar bir aile ağacıdır

Her istisna bir sınıftır ve sınıflar birbirinden türer. except X yazdığında X'in kendisini ve tüm alt türlerini yakalarsın. Bu yüzden except LookupError hem KeyError'ı hem IndexError'ı yakalar; except OSError FileNotFoundError'ı da, PermissionError'ı da.

Üst sınıfAlt sınıflardan bazılarıNe zaman yakalanır?
BaseExceptionSystemExit, KeyboardInterrupt, ExceptionNeredeyse hiç; Ctrl+C ve sys.exit buradadır
Exceptionaşağıdakilerin hepsiEn üst seviyede "beklenmeyen hata" logu için
ArithmeticErrorZeroDivisionError, OverflowErrorHesaplama hataları
LookupErrorKeyError, IndexError"Aranan yok" durumları
ValueErrorUnicodeDecodeErrorTür doğru, değer uygunsuz
OSErrorFileNotFoundError, PermissionError, FileExistsErrorDosya ve işletim sistemi
print(issubclass(KeyError, LookupError))          # True
print(issubclass(FileNotFoundError, OSError))     # True
print(issubclass(KeyboardInterrupt, Exception))   # False!
print(KeyError.__mro__)                           # soy ağacı, alttan yukarı

veriler = [{"ad": "Ayşe"}, [10, 20]]
for veri, anahtar in [(veriler[0], "telefon"), (veriler[1], 5)]:
    try:
        print(veri[anahtar])
    except LookupError as e:
        print("Bulunamadı:", type(e).__name__, e)
except Exception neden Ctrl+C'yi yakalamaz?KeyboardInterrupt ve SystemExit bilerek Exception'ın dışında, doğrudan BaseException'ın altındadır. Böylece except Exception yazsan bile kullanıcı programı durdurabilir. Çıplak except: ise BaseException'ı da yakalar; bu yüzden tehlikelidir.

except sırası: özelden genele

Python except bloklarını yukarıdan aşağıya dener ve ilk uyan bloğu çalıştırır; diğerlerine bakmaz. Genel bir tür (Exception, LookupError) özel olandan önce gelirse özel blok asla çalışmaz. Python bu durumda uyarı vermez; hata sessizce yanlış mesajla sonuçlanır.

def tutar_oku(kayit):
    try:
        return float(kayit["tutar"])
    except KeyError:
        return "tutar alanı yok"
    except ValueError:
        return "tutar sayı değil"
    except Exception as e:                 # en sona: beklenmeyenler
        return f"beklenmeyen: {type(e).__name__}"

print(tutar_oku({"tutar": "250.5"}))
print(tutar_oku({}))
print(tutar_oku({"tutar": "yok"}))
print(tutar_oku({"tutar": None}))         # float(None) → TypeError

İstisna nesnesinin içi

except ValueError as e ile aldığın e sıradan bir nesnedir: type(e).__name__ hata adını, str(e) mesajı, e.args fırlatılırken verilen argümanları verir. Raporlama için ad + mesaj ikilisini kullan. Dikkat: e adı except bloğu bitince silinir; sonra lazımsa başka bir değişkene ata.

son_hata = None
try:
    int("12,5")
except ValueError as e:
    print("Ad:", type(e).__name__)
    print("Mesaj:", e)
    print("args:", e.args)
    son_hata = e                    # e blok sonunda silinecek, sakla

print("Blok dışında:", repr(son_hata))
print("'e' hâlâ var mı?", "e" in dir())

Yeniden fırlatma ve kendi küçük istisnan

Bazen hatayı yakalayıp bir şey yapmak (log yazmak, sayaç artırmak) ama sonra yine de yukarı iletmek istersin. except içinde tek başına raise yazmak, yakalanan istisnayı traceback'iyle birlikte aynen yeniden fırlatır. İş kuralları için tek satırlık bir sınıf yeter: class StokYetersiz(Exception): pass. Çağıran taraf ValueError'larla karışmadan yalnızca bu durumu yakalayabilir.

class StokYetersiz(Exception):
    pass

stok = {"kulaklık": 3, "kablo": 40}
hata_sayaci = 0

def satis_yap(urun, adet):
    global hata_sayaci
    try:
        if stok[urun] < adet:
            raise StokYetersiz(f"{urun}: stok {stok[urun]}, istenen {adet}")
        stok[urun] -= adet
    except StokYetersiz:
        hata_sayaci += 1
        raise                       # say ve aynen yukarı ilet

for urun, adet in [("kablo", 5), ("kulaklık", 10)]:
    try:
        satis_yap(urun, adet)
        print("Satıldı:", urun)
    except StokYetersiz as e:
        print("Satış reddedildi →", e)
print("Hata sayacı:", hata_sayaci, "| Stok:", stok)

Tekrar dene (retry) kalıbı

Ağ, veritabanı ya da kilitli dosya gibi geçici hatalarda ikinci deneme çoğu zaman başarır. Kalıp: sınırlı sayıda dene, her başarısızlığı not et, son denemede de olmazsa hatayı yukarı ilet. Kalıcı hatalarda (ValueError gibi) tekrar denemek boşunadır; yalnız geçici olduğunu bildiğin türü yakala.

cevaplar = iter([ConnectionError("zaman aşımı"), ConnectionError("bağlantı koptu"), "kur=34.2"])

def kur_getir():                       # sahte servis: önce iki kez hata verir
    c = next(cevaplar)
    if isinstance(c, Exception):
        raise c
    return c

for deneme in range(1, 4):
    try:
        sonuc = kur_getir()
    except ConnectionError as e:
        print(f"Deneme {deneme} başarısız: {e}")
        if deneme == 3:
            raise
    else:
        print(f"Deneme {deneme} başarılı: {sonuc}")
        break
finally içinde return yazmafinally bloğundaki return, try'da fırlayan istisnayı da, try'daki return değerini de yutar. Hata sessizce kaybolur. finally'yi yalnız temizlik için kullan (Python 3.14 bu kullanım için uyarı verir).
Gerçek işteKurumsal bir veri betiğinde hata yönetimi üç katmanlıdır: (1) satır seviyesinde bozuk kaydı yakala, hatalılar listesine yaz ve devam et; (2) geçici dış hataları (bağlantı) sınırlı sayıda tekrar dene; (3) en üstte, ana fonksiyonda except Exception ile beklenmeyen her şeyi logla ve programı sıfırdan farklı bir çıkış koduyla bitir ki zamanlayıcı (cron, Airflow) işin başarısız olduğunu anlasın. Ortada kalan kod hatayı yakalamaz; yukarı akmasına izin verir.
Uzmanlaş

Hata bir mesajdır, gürültü değil

İyi bir hata mesajı ne beklendiğini, ne geldiğini ve mümkünse hangi kaydı söyler: ValueError("satır 42: tutar sayı olmalı, gelen 'yok'"). Gece çalışan betiğin loguna bakan kişi sorunu dakikalar içinde bulur.

Hata zinciri

Düşük seviye hatayı yakalayıp daha anlamlı bir hata fırlatırken raise YeniHata(...) from e yaz; traceback her iki hatayı da gösterir, asıl neden kaybolmaz.

Hataları say, sonra karar ver

Toplu veri işlerken ilk hatada durmak yerine hataları bir listede topla; iş bitince "1000 satırdan 3'ü atlandı" diye raporla. Kabul edilebilir hata oranı iş birimine göre değişir: kampanya e-postası için yüzde 1 sorun değil, banka mutabakatı için tek satır bile kabul edilmez. Bu kararı kodun değil, iş kuralının vermesi gerekir; sen sayıyı ölçülebilir hale getir.

Alıştırmalara geç (22)