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
| Hata | Ne zaman | Örnek |
|---|---|---|
SyntaxError | Kod daha çalışmadan: yazım hatası | eksik iki nokta, kapanmayan parantez |
NameError | Tanımsız ad | print(toplm) |
TypeError | Yanlış tür | "5" + 3 |
ValueError | Tür doğru, değer uygunsuz | int("abc") |
KeyError | Sözlükte olmayan anahtar | musteri["telefon"] |
IndexError | Listede olmayan indeks | liste[10] |
ZeroDivisionError | Sıfıra bölme | toplam / 0 |
FileNotFoundError | Dosya yok | open("yok.txt") |
AttributeError | Nesnede 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__)
except: 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
- else: try bloğu hatasız biterse çalışır. "Hata çıkabilecek kısım" ile "başarılıysa yapılacak kısım" ayrılır.
- finally: hata olsa da olmasa da, hatta except içinde return olsa bile her zaman çalışır. Kaynak kapatma, geçici dosya silme, "işlem bitti" logu için.
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.
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ş: 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ıf | Alt sınıflardan bazıları | Ne zaman yakalanır? |
|---|---|---|
BaseException | SystemExit, KeyboardInterrupt, Exception | Neredeyse hiç; Ctrl+C ve sys.exit buradadır |
Exception | aşağıdakilerin hepsi | En üst seviyede "beklenmeyen hata" logu için |
ArithmeticError | ZeroDivisionError, OverflowError | Hesaplama hataları |
LookupError | KeyError, IndexError | "Aranan yok" durumları |
ValueError | UnicodeDecodeError | Tür doğru, değer uygunsuz |
OSError | FileNotFoundError, PermissionError, FileExistsError | Dosya 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 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
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).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.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.