← Kursa Dön
📄 Text · 18 min

Exception Handling: try-except-else-finally

Programlar her zaman planladığımız gibi çalışmaz. Kullanıcı sayı yerine harf girer, dosya bulunamaz, internet bağlantısı kopar. İşte bu beklenmedik durumlarla nasıl başa çıkacağımızı öğreneceğiz.

Bu derste Python'ın hata yönetim sistemi olan exception mekanizmasını derinlemesine inceleyeceğiz. Hazırsan başlayalım!


Exception Nedir?

Exception, programın çalışması sırasında ortaya çıkan beklenmedik bir durumdur. Python bir hatayla karşılaştığında normal akışı durdurur ve bir exception (istisna) fırlatır.

🔥 Yangın Alarmı Analojisi

Exception'ları bir binadaki yangın alarm sistemi gibi düşün. Normal şartlarda bina sorunsuz çalışır — insanlar işlerini yapar, asansörler hareket eder, ışıklar yanar. Ama bir yangın çıktığında ne olur?

  1. Alarm çalar → Python bir exception fırlatır

  2. İnsanlar durur → Program normal akışını durdurur

  3. Tahliye planı devreye girer → Exception handler çalışır

  4. İtfaiye gelir → Hata uygun şekilde ele alınır

Eğer alarm sistemi yoksa (exception handling yoksa), yangın kontrolsüz yayılır ve bina çöker (program crash olur). Ama iyi bir alarm sistemiyle yangını erken tespit eder, insanları tahliye eder ve hasarı minimize edersin.

İşte exception handling tam olarak budur — programını "yangına karşı" koruma altına almak.

Hatayı Görmek

Bir exception oluştuğunda Python bize oldukça detaylı bir hata mesajı verir:

# Bu kodu çalıştırınca ne olur?
sayi = int("merhaba")

Çıktı şöyle olur:

Traceback (most recent call last):
  File "main.py", line 1, in <module>
    sayi = int("merhaba")
ValueError: invalid literal for int() with base 10: 'merhaba'

Bu hata mesajı aslında çok değerli bilgiler içerir:

  • Traceback: Hatanın nerede oluştuğunu gösterir (dosya adı, satır numarası)

  • ValueError: Hatanın türünü belirtir

  • Mesaj: Ne olduğunu açıklar

Exception handling olmadan program bu noktada tamamen durur. Altındaki hiçbir kod çalışmaz. Bu da kullanıcı için kötü bir deneyim demek.


try-except: Temel Kullanım

Python'da hataları yakalamak için try-except bloğunu kullanırız. Mantık basit: "Şunu dene, hata olursa şunu yap."

try:
    sayi = int(input("Bir sayı girin: "))
    print(f"Girdiğiniz sayı: {sayi}")
except ValueError:
    print("Hata! Lütfen geçerli bir sayı girin.")

print("Program devam ediyor...")

Bu kodda ne oluyor?

  1. try bloğundaki kod çalıştırılır

  2. Kullanıcı "abc" girerse int() bir ValueError fırlatır

  3. Python except ValueError bloğuna atlar

  4. Hata mesajı yazdırılır

  5. Program çökmeden devam eder

try-except olmadan aynı hata programı tamamen durdururdu. Ama şimdi zarif bir şekilde hatayı ele aldık.

Basit Bir Hesap Makinesi

Biraz daha gerçekçi bir örnek yapalım:

def bolenle_bol(a, b):
    try:
        sonuc = a / b
        print(f"{a} / {b} = {sonuc}")
    except ZeroDivisionError:
        print("Hata: Sıfıra bölme yapılamaz!")

bolenle_bol(10, 3)    # 10 / 3 = 3.3333...
bolenle_bol(10, 0)    # Hata: Sıfıra bölme yapılamaz!
bolenle_bol(42, 7)    # 42 / 7 = 6.0

ZeroDivisionError sadece sıfıra bölme durumunda tetiklenir. Başka bir hata türü oluşursa bu except bloğu onu yakalamaz — ve bu aslında iyi bir şey. Neden mi? Çünkü her hatayı aynı şekilde ele almak istemezsin.


Birden Fazla except Bloğu

Bir try bloğunda farklı türde hatalar oluşabilir. Her biri için ayrı bir except bloğu yazabilirsin:

def guvenli_eris(liste, index):
    try:
        deger = liste[index]
        sonuc = 10 / deger
        return sonuc
    except IndexError:
        print(f"Hata: {index} indeksi liste dışında!")
    except ZeroDivisionError:
        print("Hata: Listede sıfır değeri var, bölme yapılamaz!")
    except TypeError:
        print("Hata: Listede sayısal olmayan değer var!")

sayilar = [5, 0, "abc", 2]
guvenli_eris(sayilar, 0)    # 2.0 döner
guvenli_eris(sayilar, 1)    # Sıfıra bölme hatası
guvenli_eris(sayilar, 2)    # TypeError
guvenli_eris(sayilar, 10)   # IndexError

Python except bloklarını yukarıdan aşağıya kontrol eder. İlk eşleşen except bloğu çalışır ve diğerleri atlanır. Bu yüzden sıralama önemli olabilir.

Birden Fazla Exception'ı Tek Blokta Yakalama

Bazen farklı hata türleri için aynı işlemi yapmak istersin. Bu durumda tuple kullanabilirsin:

def sayi_cevir(deger):
    try:
        return float(deger)
    except (ValueError, TypeError):
        print(f"'{deger}' sayıya çevrilemedi!")
        return None

print(sayi_cevir("3.14"))    # 3.14
print(sayi_cevir("hello"))   # Hata mesajı, None
print(sayi_cevir(None))      # Hata mesajı, None

(ValueError, TypeError) parantezlerine dikkat et — bu bir tuple. İkisinden biri oluşursa aynı except bloğu çalışır.


else Bloğu: Hata Olmazsa Çalışır

try-except yapısına bir de else ekleyebilirsin. else bloğu sadece hiçbir exception oluşmadığında çalışır.

"Ama try bloğunun sonuna da yazabilirdim?" diyebilirsin. Teknik olarak evet, ama else kullanmanın önemli bir avantajı var: else bloğundaki hatalar except tarafından yakalanmaz. Bu sayede hangi kodun korunduğunu ve hangisinin korunmadığını net bir şekilde ayırırsın.

def dosya_oku(dosya_adi):
    try:
        dosya = open(dosya_adi, "r")
    except FileNotFoundError:
        print(f"'{dosya_adi}' bulunamadı!")
    else:
        # Dosya başarıyla açıldıysa burası çalışır
        icerik = dosya.read()
        print(f"Dosya içeriği ({len(icerik)} karakter):")
        print(icerik[:100])  # İlk 100 karakter
        dosya.close()

dosya_oku("mevcut_dosya.txt")    # Dosyayı okur
dosya_oku("olmayan_dosya.txt")   # Hata mesajı verir

try-except-else Akış Şeması

try bloğu çalışır
    ├── Hata OLURSA → except bloğu çalışır
    └── Hata OLMAZSA → else bloğu çalışır

else bloğu şu durumlarda çok işe yarar:

  • Başarılı durumda ek işlem yapmak istediğinde

  • try bloğunu mümkün olduğunca kısa tutmak istediğinde

  • Hangi kodun "korumalı" olduğunu açıkça belirtmek istediğinde


finally Bloğu: Her Durumda Çalışır

finally bloğu ne olursa olsun çalışır — hata olsun ya da olmasın, return ifadesi olsa bile.

Bu genellikle temizlik işlemleri için kullanılır: dosya kapatma, veritabanı bağlantısını kesme, kilit bırakma gibi.

def veritabani_sorgusu(sorgu):
    baglanti = None
    try:
        baglanti = veritabani_baglan()  # Bağlantı kur
        sonuc = baglanti.calistir(sorgu)
        return sonuc
    except BaglantiHatasi:
        print("Veritabanına bağlanılamadı!")
    except SorguHatasi:
        print("SQL sorgusu hatalı!")
    finally:
        # Bağlantı açıldıysa MUTLAKA kapat
        if baglanti:
            baglanti.kapat()
            print("Bağlantı kapatıldı.")

finally bloğunun gücünü daha basit bir örnekle görelim:

def bolme_yap(a, b):
    try:
        sonuc = a / b
        return sonuc
    except ZeroDivisionError:
        print("Sıfıra bölünemez!")
        return None
    finally:
        print("İşlem tamamlandı.")  # HER ZAMAN çalışır

print(bolme_yap(10, 2))
# Çıktı:
# İşlem tamamlandı.
# 5.0

print(bolme_yap(10, 0))
# Çıktı:
# Sıfıra bölünemez!
# İşlem tamamlandı.
# None

Dikkat et: return ifadesinden önce finally çalışıyor! Python try bloğundan çıkmadan önce finally'yi mutlaka çalıştırır.

Komple Yapı: try-except-else-finally

def guvenli_bolme(a, b):
    try:
        sonuc = a / b
    except ZeroDivisionError:
        print("Hata: Sıfıra bölünemez!")
        sonuc = None
    except TypeError:
        print("Hata: Sayısal değer gerekli!")
        sonuc = None
    else:
        print(f"Başarılı: {a} / {b} = {sonuc}")
    finally:
        print("--- İşlem bitti ---")
    return sonuc

Akış şeması:

try → Hata var mı?
  ├── EVET → except çalışır → finally çalışır
  └── HAYIR → else çalışır → finally çalışır

💡 İpucu: finally genelde with statement ile birlikte kullanılmaz çünkü with zaten otomatik temizlik yapar. finally'yi with kullanamadığın durumlarda tercih et.


Exception Hiyerarşisi

Python'daki tüm exception'lar bir sınıf hiyerarşisi oluşturur. Bu hiyerarşiyi anlamak, doğru exception'ı yakalamanın anahtarıdır.

BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
    ├── ArithmeticError
    │   ├── ZeroDivisionError
    │   ├── OverflowError
    │   └── FloatingPointError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── ValueError
    ├── TypeError
    ├── OSError
    │   ├── FileNotFoundError
    │   ├── PermissionError
    │   └── FileExistsError
    ├── AttributeError
    ├── NameError
    ├── RuntimeError
    │   └── RecursionError
    └── StopIteration

Bu Hiyerarşi Neden Önemli?

Bir except bloğu belirttiğin exception'ı ve onun tüm alt sınıflarını yakalar:

try:
    sozluk = {"ad": "Python"}
    print(sozluk["yas"])
except LookupError:
    # Hem KeyError hem IndexError'u yakalar!
    print("Arama hatası!")

LookupError, KeyError'ın üst sınıfıdır. Bu yüzden except LookupError yazınca KeyError da yakalanır.

Bu durumun tehlikeli bir versiyonu:

# ÇOK GENİŞ — bunu yapma!
try:
    # ... bir sürü kod ...
    pass
except BaseException:
    # KeyboardInterrupt bile yakalanır!
    # Ctrl+C ile programı durduramaz olursun!
    print("Bir şey oldu")

BaseException yakalamak KeyboardInterrupt'ı da yakalar, yani kullanıcı Ctrl+C ile programı durduramaz. Bu çok kötü bir pratik.


Yaygın Exception Türleri

Python'da en sık karşılaşacağın exception türlerini tanıyalım:

ValueError — Yanlış Değer

Doğru tür ama yanlış değer verildiğinde:

# Sayıya çevrilemeyen string
int("merhaba")       # ValueError
int("12.5")           # ValueError (int için geçersiz)

# Geçersiz değer
import math
math.sqrt(-1)         # ValueError

TypeError — Yanlış Tür

Yanlış veri türü kullanıldığında:

# String ile sayı toplama
"5" + 3               # TypeError

# Çağrılamayan nesneyi çağırma
x = 42
x()                   # TypeError: 'int' object is not callable

# Yanlış argüman sayısı
len(1, 2, 3)          # TypeError

KeyError — Sözlükte Olmayan Anahtar

ogrenci = {"ad": "Ali", "yas": 20}
print(ogrenci["not"])  # KeyError: 'not'

IndexError — Liste Dışı İndeks

meyveler = ["elma", "armut", "muz"]
print(meyveler[5])     # IndexError: list index out of range

FileNotFoundError — Dosya Bulunamadı

with open("olmayan_dosya.txt") as f:
    pass               # FileNotFoundError

AttributeError — Olmayan Özellik/Metot

sayi = 42
sayi.upper()           # AttributeError: 'int' has no attribute 'upper'

NameError — Tanımsız Değişken

print(tanimlanmamis_degisken)  # NameError

ZeroDivisionError — Sıfıra Bölme

sonuc = 10 / 0         # ZeroDivisionError

Bu hataları bilmek, doğru except bloğunu yazmak için çok önemli. Her hatanın bir nedeni var ve o nedene göre farklı aksiyonlar alman gerekir.


Bare except Neden Kötü?

"Her hatayı yakalayayım" düşüncesiyle şöyle kod yazan çok var:

# KÖTÜ PRATİK — bunu yapma!
try:
    sonuc = hesapla(veri)
except:
    print("Bir hata oluştu")

Bu "bare except" (çıplak except) diye adlandırılır ve ciddi sorunlara yol açar:

  1. KeyboardInterrupt yakalanır — Ctrl+C çalışmaz

  2. SystemExit yakalanırsys.exit() çalışmaz

  3. Hangi hata olduğunu bilemezsin — debug etmek imkansızlaşır

  4. Gerçek bugları gizler — Programda bir hata var ama göremiyorsun

# YANLIŞ — Bare except
try:
    veri = dosyadan_oku()
    islenmiş = isle(veri)
    kaydet(islenmiş)
except:
    print("Bir hata oluştu")  # Hangi hata? Nerede? Neden?

Bunun yerine ne yapmalı?

# DOĞRU — Spesifik exception'lar
try:
    veri = dosyadan_oku()
    islenmiş = isle(veri)
    kaydet(islenmiş)
except FileNotFoundError:
    print("Dosya bulunamadı!")
except ValueError as e:
    print(f"Veri işleme hatası: {e}")
except IOError as e:
    print(f"Kaydetme hatası: {e}")

Eğer gerçekten "tüm exception'ları" yakalamak istiyorsan (ki nadiren gerekir), en azından Exception kullan:

# KABUL EDİLEBİLİR — Exception kullanımı
try:
    riskli_islem()
except Exception as e:
    print(f"Beklenmeyen hata: {e}")
    # Hatayı logla, sonra gerekirse tekrar fırlat
    raise

except Exception en azından KeyboardInterrupt ve SystemExit gibi sistem exception'larını yakalamaz. Bu çok önemli bir fark.

⚠️ Dikkat: Bare except (except:) yerine her zaman except Exception: kullan. Daha da iyisi, spesifik exception türlerini yakala. Bu debugging sürecini çok kolaylaştırır ve beklenmedik hataları gizlemeni engeller.


raise ile Hata Fırlatma

Sadece hata yakalamak yetmez — bazen kendi hatalarını fırlatman da gerekir. raise keyword'ü tam olarak bunu yapar.

Basit raise Kullanımı

def yas_kontrol(yas):
    if yas < 0:
        raise ValueError("Yaş negatif olamaz!")
    if yas > 150:
        raise ValueError("Geçersiz yaş değeri!")
    return f"Yaşınız: {yas}"

try:
    print(yas_kontrol(25))    # Yaşınız: 25
    print(yas_kontrol(-5))    # ValueError fırlatılır
except ValueError as e:
    print(f"Hata: {e}")

raise ile istediğin exception türünü fırlatabilirsin. Fonksiyonun girdilerini doğrulamak için çok kullanılır.

raise ile Hatayı Tekrar Fırlatma

Bazen bir hatayı yakalayıp bir şeyler yaptıktan sonra tekrar fırlatmak istersin:

import logging

def veritabani_kaydet(veri):
    try:
        # Veritabanına kaydetmeyi dene
        _kaydet(veri)
    except Exception as e:
        # Hatayı logla
        logging.error(f"Kaydetme hatası: {e}")
        # Hatayı tekrar fırlat — çağıran fonksiyon da bilsin
        raise

# Kullanım
try:
    veritabani_kaydet({"ad": "Ali"})
except Exception:
    print("Kaydetme başarısız, yedek sisteme geçiliyor...")

Tek başına raise (argümansız) yazınca, o an yakalanan exception aynen tekrar fırlatılır. Bu, hatayı loglayıp sonra üst katmana iletmek için mükemmel bir pattern.

Koşullu raise

def indirimli_fiyat(fiyat, indirim_orani):
    if not isinstance(fiyat, (int, float)):
        raise TypeError(f"Fiyat sayısal olmalı, {type(fiyat).__name__} verildi")
    if not 0 <= indirim_orani <= 100:
        raise ValueError(f"İndirim oranı 0-100 arası olmalı, {indirim_orani} verildi")
    
    return fiyat * (1 - indirim_orani / 100)

try:
    print(indirimli_fiyat(100, 20))     # 80.0
    print(indirimli_fiyat(100, 150))    # ValueError
except (TypeError, ValueError) as e:
    print(f"Geçersiz girdi: {e}")

raise kullanırken mesaj vermek iyi bir pratik. Bu mesaj as e ile yakalandığında str(e) olarak okunabilir.


as Keyword: Hata Nesnesine Erişim

Exception yakaladığında genellikle hata hakkında bilgi almak istersin. as keyword'ü exception nesnesini bir değişkene atar:

try:
    sozluk = {"Python": 1991, "Java": 1995}
    print(sozluk["JavaScript"])
except KeyError as e:
    print(f"Anahtar bulunamadı: {e}")
    print(f"Hata türü: {type(e).__name__}")
    print(f"Hata argümanları: {e.args}")

Çıktı:

Anahtar bulunamadı: 'JavaScript'
Hata türü: KeyError
Hata argümanları: ('JavaScript',)

Exception Nesnesinin Özellikleri

Her exception nesnesi bazı yararlı özelliklere sahiptir:

try:
    with open("/root/gizli_dosya.txt") as f:
        pass
except PermissionError as e:
    print(f"Mesaj: {e}")
    print(f"Args: {e.args}")
    print(f"Tür: {type(e).__name__}")
    print(f"Errno: {e.errno}")        # OS hata kodu
    print(f"Strerror: {e.strerror}")   # OS hata mesajı
    print(f"Filename: {e.filename}")   # Dosya adı

OSError ve alt sınıfları (FileNotFoundError, PermissionError vb.) ekstra özellikler taşır: errno, strerror, filename.

Birden Fazla except ile as Kullanımı

def veri_isle(deger):
    try:
        sayi = int(deger)
        sonuc = 100 / sayi
        return sonuc
    except ValueError as e:
        print(f"Değer hatası: {e}")
        return None
    except ZeroDivisionError as e:
        print(f"Bölme hatası: {e}")
        return None
    except TypeError as e:
        print(f"Tür hatası: {e}")
        return None

print(veri_isle("abc"))    # Değer hatası: invalid literal...
print(veri_isle("0"))      # Bölme hatası: division by zero
print(veri_isle(None))     # Tür hatası: int() argument...

Her except bloğu kendi as değişkenine sahip. Bu değişken sadece o except bloğu içinde geçerli — blok dışında erişilemez (Python 3'te bu değişken blok sonunda silinir).


Pratik: Güvenli Input Alma

Şimdi öğrendiklerimizi birleştirelim. Kullanıcıdan güvenli bir şekilde sayı alalım:

def sayi_al(mesaj, min_deger=None, max_deger=None, max_deneme=3):
    """Kullanıcıdan güvenli sayı girişi alır."""
    for deneme in range(max_deneme):
        try:
            deger = float(input(mesaj))
            
            if min_deger is not None and deger < min_deger:
                raise ValueError(f"Değer en az {min_deger} olmalı")
            if max_deger is not None and deger > max_deger:
                raise ValueError(f"Değer en fazla {max_deger} olmalı")
            
            return deger
            
        except ValueError as e:
            kalan = max_deneme - deneme - 1
            if kalan > 0:
                print(f"Geçersiz: {e}. {kalan} hakkınız kaldı.")
            else:
                print("Çok fazla hatalı deneme!")
    
    return None

# Kullanım
yas = sayi_al("Yaşınızı girin: ", min_deger=0, max_deger=150)
if yas is not None:
    print(f"Yaşınız: {yas}")
else:
    print("Yaş alınamadı.")

Bu fonksiyon şunları yapıyor:

  • Geçersiz girdi yakalanır (ValueError)

  • Aralık kontrolü yapılır (çok küçük/büyük)

  • Deneme sayısı sınırlanır

  • Her durumda anlamlı geri bildirim verilir

Hesap Makinesi Örneği

def hesap_makinesi():
    """Basit ama güvenli hesap makinesi."""
    islemler = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b,
    }
    
    print("Basit Hesap Makinesi")
    print("Çıkmak için 'q' yazın.\n")
    
    while True:
        try:
            girdi = input("İşlem (ör: 10 + 5): ").strip()
            
            if girdi.lower() == "q":
                print("Güle güle!")
                break
            
            parcalar = girdi.split()
            if len(parcalar) != 3:
                raise ValueError("Format: sayı işlem sayı")
            
            a = float(parcalar[0])
            islem = parcalar[1]
            b = float(parcalar[2])
            
            if islem not in islemler:
                raise ValueError(f"Geçersiz işlem: {islem}")
            
            sonuc = islemler[islem](a, b)
            print(f"Sonuç: {sonuc}\n")
            
        except ValueError as e:
            print(f"Hata: {e}\n")
        except ZeroDivisionError:
            print("Hata: Sıfıra bölünemez!\n")
        except KeyboardInterrupt:
            print("\nProgram sonlandırıldı.")
            break

hesap_makinesi()

Bu örnekte dikkat edilecek noktalar:

  • Her hata türü için farklı mesaj

  • KeyboardInterrupt ayrıca ele alınıyor (Ctrl+C)

  • while True döngüsü — hata olsa bile program devam ediyor

  • raise ValueError(...) ile kendi doğrulama hatalarımızı fırlatıyoruz


İç İçe try-except

Bazen try-except bloklarını iç içe kullanman gerekebilir:

def dosya_isle(dosya_adi):
    try:
        with open(dosya_adi, "r") as f:
            for satir_no, satir in enumerate(f, 1):
                try:
                    sayi = float(satir.strip())
                    print(f"Satır {satir_no}: {sayi * 2}")
                except ValueError:
                    print(f"Satır {satir_no}: '{satir.strip()}' sayı değil, atlanıyor")
    except FileNotFoundError:
        print(f"Dosya bulunamadı: {dosya_adi}")
    except PermissionError:
        print(f"Dosya okuma izni yok: {dosya_adi}")

Dıştaki try-except dosya açma hatalarını yakalar. İçteki try-except her satırı ayrı ayrı işler — bir satırda hata olması diğer satırların işlenmesini engellemez.

Bu pattern özellikle dosya işleme ve veri dönüştürme senaryolarında çok işe yarar. Her kaydı bağımsız olarak ele almak, bir kayıttaki hatanın tüm işlemi çökertmesini engeller.


Exception Handling'de Performans

Merak edebilirsin: "try-except kullanmak programımı yavaşlatır mı?"

Kısa cevap: Hata olmadığında neredeyse hiç yavaşlatmaz. Python, try bloğuna girmek için çok az ek maliyet öder. Asıl maliyet bir exception fırlatıldığında ortaya çıkar.

import time

# try-except ile (hata yok)
baslangic = time.time()
for _ in range(1_000_000):
    try:
        x = 1 + 1
    except:
        pass
sure_try = time.time() - baslangic

# try-except olmadan
baslangic = time.time()
for _ in range(1_000_000):
    x = 1 + 1
sure_normal = time.time() - baslangic

print(f"try-except ile: {sure_try:.4f}s")
print(f"Normal: {sure_normal:.4f}s")
# Fark yok denecek kadar az!

Ama exception fırlatılması pahalıdır. Bu yüzden exception'ları normal program akışı için kullanma. Exception'lar adı üstünde istisnai durumlar içindir.

# KÖTÜ — Exception'ı kontrol akışı olarak kullanma
for i in range(100):
    try:
        deger = sozluk[i]
    except KeyError:
        pass

# İYİ — Önce kontrol et
for i in range(100):
    if i in sozluk:
        deger = sozluk[i]

💡 İpucu: Bu kural her zaman geçerli değildir. Python topluluğunda "EAFP" (Easier to Ask Forgiveness than Permission) prensibi yaygındır — yani "izin sormak yerine affetmek kolaydır." Bunu bir sonraki derste detaylıca işleyeceğiz.


Yaygın Hatalar ve Anti-Pattern'ler

1. Çok Geniş try Bloğu

# KÖTÜ — Çok fazla kod try içinde
try:
    dosya = open("veri.txt")
    icerik = dosya.read()
    veri = json.loads(icerik)
    sonuc = isle(veri)
    kaydet(sonuc)
    bildirim_gonder(sonuc)
except Exception:
    print("Bir şeyler yanlış gitti")  # Ama ne?

# İYİ — Sadece riskli kodu try içine al
try:
    dosya = open("veri.txt")
    icerik = dosya.read()
except FileNotFoundError:
    print("Dosya bulunamadı")
else:
    try:
        veri = json.loads(icerik)
    except json.JSONDecodeError:
        print("Geçersiz JSON formatı")
    else:
        sonuc = isle(veri)
        kaydet(sonuc)

2. Hatayı Sessizce Yutma

# KÖTÜ — Hatayı yut, hiçbir şey yapma
try:
    riskli_islem()
except Exception:
    pass  # 🙈 Hata? Hangi hata?

# İYİ — En azından logla
try:
    riskli_islem()
except Exception as e:
    logging.warning(f"İşlem başarısız: {e}")
    # veya: raise (tekrar fırlat)

3. Exception'ı Yanlış Yerde Yakalama

# KÖTÜ — Her fonksiyon kendi hatasını yutuyor
def adim1():
    try:
        return riskli_is()
    except Exception:
        return None  # Hata bilgisi kayboldu

def adim2(veri):
    try:
        return isle(veri)
    except Exception:
        return None  # Yine kayboldu

# İYİ — Hataların yukarı çıkmasına izin ver
def adim1():
    return riskli_is()  # Hata olursa fırlasın

def adim2(veri):
    return isle(veri)   # Hata olursa fırlasın

# Ana fonksiyonda yakala
try:
    v = adim1()
    sonuc = adim2(v)
except RiskliIsHatasi:
    print("Adım 1 başarısız")
except IslemeHatasi:
    print("Adım 2 başarısız")

⚠️ Dikkat: Exception'ları mümkün olduğunca yukarıda (çağıran kodda) yakala. Her fonksiyonun kendi hatasını yutması, debugging'i kabusa çevirir. Hataların akmasına izin ver, en uygun yerde yakala.


Özet

Bu derste Python'ın exception handling mekanizmasını derinlemesine inceledik. İşte önemli noktalar:

  • Exception programın çalışma anında oluşan hatalardır; "yangın alarmı" gibi düşün — erken tespit et, doğru müdahale et.

  • try-except temel hata yakalama yapısıdır. try bloğuna riskli kodu, except bloğuna hata durumunda yapılacak işlemi yaz.

  • else bloğu hata olmadığında, finally bloğu her durumda çalışır. finally özellikle kaynak temizliği (dosya kapatma, bağlantı kesme) için kullanılır.

  • Spesifik exception yakalaexcept: (bare except) yerine except ValueError: gibi spesifik türler kullan. Bu hem debugging'i kolaylaştırır hem KeyboardInterrupt gibi sistem exception'larını yanlışlıkla yakalamayı engeller.

  • raise ile kendi hatalarını fırlatabilirsin. Input doğrulama ve iş kuralı ihlallerinde çok kullanılır. raise (argümansız) yakalanan hatayı tekrar fırlatır.

  • as keyword'ü ile exception nesnesine erişerek hata mesajı, türü ve ek bilgileri okuyabilirsin. Bu bilgiler loglama ve kullanıcı geri bildirimi için çok değerli.