← Kursa Dön
📄 Text · 15 min

Custom Exceptions ve Best Practices

Önceki derste Python'ın yerleşik exception'larını öğrendik. Ama gerçek projelerde bazen ValueError ya da TypeError yetmez — kendi anlamlı hata türlerini oluşturman gerekir. Bu derste custom exception'lar, exception chaining ve hata yönetimi best practice'lerini keşfedeceğiz.


Neden Custom Exception?

Python'ın yerleşik exception'ları genel amaçlıdır. Ama diyelim ki bir bankacılık uygulaması yazıyorsun:

def para_cek(hesap, miktar):
    if miktar > hesap.bakiye:
        raise ValueError("Yetersiz bakiye")

Bu çalışır ama sorunlu. ValueError çok genel — kodun herhangi bir yerinde oluşabilir. Bunu yakalayan kişi "Bu ValueError bakiye yetersizliğinden mi, yoksa geçersiz bir girişten mi?" diye düşünmek zorunda kalır.

🏷️ Etiketli Kutu Analojisi

Custom exception'ları etiketli kutular gibi düşün. Bir depoda her şeyi aynı kahverengi kutuya koyarsan, aradığını bulmak imkansızlaşır. Ama kutuların üzerinde "Elektronik", "Mutfak Eşyası", "Kırtasiye" yazıyorsa? İstediğini anında bulursun.

ValueError etiketlenmemiş bir kutu. InsufficientBalanceError ise üzerinde "Yetersiz Bakiye" yazan, tam olarak ne içerdiğini bildiğin bir kutu.

Custom exception'ların avantajları:

  • Okunabilirlik: Kod okuyan kişi hatanın ne olduğunu anında anlar

  • Spesifik yakalama: Sadece ilgilendiğin hataları yakalarsın

  • Ek bilgi: Hataya özel veri ekleyebilirsin (miktar, hesap no, tarih...)

  • API tasarımı: Kütüphane/modül kullanıcılarına net hata bilgisi verirsin


Custom Exception Tanımlama

Custom exception tanımlamak çok basit — Exception sınıfından türetmek yeterli:

class YetersizBakiyeError(Exception):
    """Hesapta yeterli bakiye olmadığında fırlatılır."""
    pass

class GecersizTutarError(Exception):
    """Geçersiz para tutarı girildiğinde fırlatılır."""
    pass

Bu kadar! Şimdi bu exception'ları kullanabiliriz:

def para_cek(hesap_bakiye, miktar):
    if miktar <= 0:
        raise GecersizTutarError(f"Tutar pozitif olmalı: {miktar}")
    if miktar > hesap_bakiye:
        raise YetersizBakiyeError(
            f"{miktar} TL çekilemez, bakiye: {hesap_bakiye} TL"
        )
    return hesap_bakiye - miktar

# Kullanım
try:
    yeni_bakiye = para_cek(1000, 1500)
except YetersizBakiyeError as e:
    print(f"Bakiye yetersiz: {e}")
except GecersizTutarError as e:
    print(f"Geçersiz tutar: {e}")

Artık her hata türünü ayrı ayrı yakalayıp farklı aksiyonlar alabilirsin.

İsimlendirme Kuralı

Python'da exception isimleri her zaman `Error` ile biter (convention):

# DOĞRU
class DatabaseConnectionError(Exception): ...
class InvalidEmailError(Exception): ...
class AuthenticationError(Exception): ...

# YANLIŞ (çalışır ama convention dışı)
class DatabaseProblem(Exception): ...    # "Error" yok
class invalid_email(Exception): ...      # snake_case değil, PascalCase olmalı

Exception'a Mesaj ve Ekstra Veri Ekleme

Basit bir pass ile tanımlanan exception yeterli olabilir ama çoğu zaman ekstra veri taşımak istersin:

class YetersizBakiyeError(Exception):
    def __init__(self, hesap_no, bakiye, istenen_miktar):
        self.hesap_no = hesap_no
        self.bakiye = bakiye
        self.istenen_miktar = istenen_miktar
        self.eksik = istenen_miktar - bakiye
        
        mesaj = (
            f"Hesap {hesap_no}: {istenen_miktar} TL çekilemez. "
            f"Mevcut bakiye: {bakiye} TL, "
            f"Eksik: {self.eksik} TL"
        )
        super().__init__(mesaj)

Şimdi bu exception'ı yakaladığında ekstra bilgilere erişebilirsin:

try:
    raise YetersizBakiyeError("TR001", 500, 750)
except YetersizBakiyeError as e:
    print(e)
    # Hesap TR001: 750 TL çekilemez. Mevcut bakiye: 500 TL, Eksik: 250 TL
    
    print(f"Hesap: {e.hesap_no}")
    print(f"Bakiye: {e.bakiye}")
    print(f"İstenen: {e.istenen_miktar}")
    print(f"Eksik tutar: {e.eksik}")

super().__init__(mesaj) çağrısı önemli — bu sayede str(e) dediğinde güzel bir mesaj alırsın.

Exception Hiyerarşisi Oluşturma

Büyük projelerde exception'larını bir hiyerarşi halinde organize etmek iyi bir pratik:

# Temel exception — tüm proje exception'larının atası
class BankaHatasi(Exception):
    """Bankacılık uygulamasının temel exception sınıfı."""
    pass

# Alt exception'lar
class HesapHatasi(BankaHatasi):
    """Hesapla ilgili hatalar."""
    pass

class YetersizBakiyeError(HesapHatasi):
    """Yetersiz bakiye."""
    pass

class HesapDondurulmusError(HesapHatasi):
    """Dondurulmuş hesap."""
    pass

class KimlikDogrulamaHatasi(BankaHatasi):
    """Kimlik doğrulama hataları."""
    pass

class GecersizSifreError(KimlikDogrulamaHatasi):
    """Geçersiz şifre."""
    pass

Bu hiyerarşinin güzelliği: farklı seviyelerde yakalama yapabilirsin.

try:
    banka_islemi()
except YetersizBakiyeError:
    # Sadece bakiye yetersizliği
    print("Bakiye yetersiz")
except HesapHatasi:
    # Tüm hesap hataları (bakiye, dondurulmuş vs.)
    print("Hesap hatası")
except BankaHatasi:
    # Tüm banka hataları
    print("Banka sistemi hatası")

Bu yapı kütüphane yazarken özellikle değerli. Kullanıcılar istedikleri detay seviyesinde hata yakalayabilir.


Exception Chaining: raise ... from ...

Bazen bir exception yakalar ve farklı bir exception fırlatırsın. Bu durumda orijinal hatanın kaybolmaması önemli. Python 3'te raise ... from ... syntax'ı bunu sağlar.

class VeriIslemeHatasi(Exception):
    """Veri işleme sırasında oluşan hata."""
    pass

def json_oku(dosya_yolu):
    import json
    try:
        with open(dosya_yolu) as f:
            return json.load(f)
    except FileNotFoundError as e:
        raise VeriIslemeHatasi(
            f"Yapılandırma dosyası bulunamadı: {dosya_yolu}"
        ) from e
    except json.JSONDecodeError as e:
        raise VeriIslemeHatasi(
            f"Geçersiz JSON formatı: {dosya_yolu}"
        ) from e

from e ifadesi, yeni exception'ın orijinal hataya bağlı olduğunu belirtir. Traceback'te her iki hata da görünür:

FileNotFoundError: [Errno 2] No such file or directory: 'config.json'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  ...
VeriIslemeHatasi: Yapılandırma dosyası bulunamadı: config.json

from None ile Zinciri Kırma

Bazen orijinal hatayı gizlemek istersin — özellikle iç detayları dışarıya sızdırmak istemediğinde:

def kullanici_bul(kullanici_id):
    try:
        # İç implementasyon detayı — kullanıcı bilmemeli
        return veritabani.sorgula(f"SELECT * FROM users WHERE id={kullanici_id}")
    except DatabaseInternalError:
        raise KullaniciBulunamadiError(
            f"Kullanıcı bulunamadı: {kullanici_id}"
        ) from None  # Orijinal hatayı gizle

from None kullanıldığında traceback'te sadece yeni exception görünür. Veritabanı detayları (tablo adı, SQL sorgusu) dışarıya sızmaz.

Implicit Chaining

from kullanmasan bile, bir except bloğu içinde yeni exception fırlatırsan Python otomatik olarak zincirler:

try:
    int("abc")
except ValueError:
    raise RuntimeError("Dönüştürme başarısız")
    # "During handling of the above exception, another exception occurred:"

Ama from kullanmak niyetini açıkça belirtir: "Bu hata, şu hatadan kaynaklanıyor."


Exception Best Practices

Yılların deneyimiyle oluşmuş en iyi pratikleri inceleyelim.

1. Spesifik Exception Yakala

# KÖTÜ
try:
    veri = isle(girdi)
except Exception:
    print("Hata oluştu")

# İYİ
try:
    veri = isle(girdi)
except ValueError:
    print("Geçersiz değer")
except KeyError:
    print("Anahtar bulunamadı")

Ne kadar spesifik yakalarsan, hataları o kadar iyi ele alırsın. Genel exception yakalamak bug'ları gizler.

2. try Bloğunu Kısa Tut

# KÖTÜ — Çok fazla kod try içinde
try:
    dosya = open("veri.json")
    icerik = dosya.read()
    dosya.close()
    veri = json.loads(icerik)
    sonuc = hesapla(veri)
    raporla(sonuc)
    bildirim_gonder()
except Exception as e:
    print(f"Hata: {e}")

# İYİ — Sadece riskli kısmı koru
try:
    with open("veri.json") as f:
        veri = json.load(f)
except FileNotFoundError:
    print("Dosya bulunamadı")
    veri = varsayilan_veri()
except json.JSONDecodeError:
    print("JSON formatı hatalı")
    veri = varsayilan_veri()

# Geri kalan try dışında
sonuc = hesapla(veri)
raporla(sonuc)

3. EAFP vs LBYL

Python'da iki farklı hata yönetim stili var:

LBYL (Look Before You Leap — "Atlamadan Önce Bak"):

# LBYL stili — önce kontrol et
if "anahtar" in sozluk:
    deger = sozluk["anahtar"]
else:
    deger = "varsayilan"

if os.path.exists(dosya_yolu):
    with open(dosya_yolu) as f:
        icerik = f.read()

EAFP (Easier to Ask Forgiveness than Permission — "İzin Almaktansa Af Dilemek Kolay"):

# EAFP stili — dene, hata olursa yakala
try:
    deger = sozluk["anahtar"]
except KeyError:
    deger = "varsayilan"

try:
    with open(dosya_yolu) as f:
        icerik = f.read()
except FileNotFoundError:
    icerik = ""

Python topluluğu genellikle EAFP'yi tercih eder. Neden?

  • Race condition yok: LBYL'de kontrol ile kullanım arasında durum değişebilir (dosya silinebilir)

  • Pythonic: Python'ın doğasına daha uygun

  • Performans: Hata nadir oluşuyorsa EAFP daha hızlı (kontrol maliyeti yok)

  • Duck typing: "Ördek gibi yürüyorsa, ördek gibi vaklıyorsa, ördektir"

Ama LBYL'in de yeri var — özellikle kontrolün basit ve hatanın sık oluşacağı durumlarda:

# LBYL burada daha mantıklı
if kullanici_yas >= 18:
    alkol_sat()
else:
    print("Yaş sınırı!")

💡 İpucu: Genel kural: Hata nadir oluşuyorsa EAFP kullan, sık oluşuyorsa LBYL kullan. Python'da EAFP daha yaygın ve tercih edilen stildir, ama fanatik olma — duruma göre en okunaklı olanı seç.

4. Hatayı Doğru Seviyede Yakala

Bir fonksiyon zincirinde hatayı en anlamlı yerde yakala:

# Alt seviye fonksiyon — hatayı fırlatsın
def dosya_oku(yol):
    with open(yol) as f:
        return f.read()

# Orta seviye — dönüştürme hatalarını ele alsın
def veri_yukle(yol):
    icerik = dosya_oku(yol)  # FileNotFoundError yukarı çıksın
    try:
        return json.loads(icerik)
    except json.JSONDecodeError as e:
        raise VeriFormatiHatasi(f"JSON hatası: {yol}") from e

# Üst seviye — kullanıcıyla etkileşim
def ana_islem():
    try:
        veri = veri_yukle("config.json")
    except FileNotFoundError:
        print("Yapılandırma dosyası bulunamadı!")
    except VeriFormatiHatasi as e:
        print(f"Yapılandırma hatası: {e}")

logging ile Hata Kaydetme

print() ile hata mesajı yazmak geliştirme sırasında işe yarar ama production'da yetersiz kalır. Python'ın logging modülü profesyonel hata kaydetme için kullanılır:

import logging

# Logger ayarla
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    filename="uygulama.log"
)

logger = logging.getLogger(__name__)

def kullanici_giris(kullanici_adi, sifre):
    try:
        kullanici = veritabani_bul(kullanici_adi)
        if not sifre_dogrula(kullanici, sifre):
            logger.warning(f"Başarısız giriş denemesi: {kullanici_adi}")
            raise KimlikDogrulamaHatasi("Geçersiz şifre")
        logger.info(f"Başarılı giriş: {kullanici_adi}")
        return kullanici
    except VeriTabaniHatasi as e:
        logger.error(f"Veritabanı hatası: {e}", exc_info=True)
        raise

Logging Seviyeleri

logger.debug("Detay bilgisi — geliştirme sırasında")
logger.info("Bilgi — normal işlem akışı")
logger.warning("Uyarı — potansiyel sorun")
logger.error("Hata — bir şey yanlış gitti")
logger.critical("Kritik — sistem çökebilir")

exc_info ile Traceback Loglama

try:
    riskli_islem()
except Exception as e:
    # exc_info=True traceback'i de loglar
    logger.error("İşlem başarısız", exc_info=True)

Bu log dosyasında tam traceback'i gösterir — debug için hayat kurtarır.

# Alternatif: logger.exception() — otomatik exc_info=True
try:
    riskli_islem()
except Exception as e:
    logger.exception("İşlem başarısız")  # Traceback otomatik eklenir

traceback Modülü

traceback modülü exception bilgilerini programatik olarak işlemeni sağlar:

import traceback

def hatali_fonksiyon():
    return 1 / 0

def cagiran_fonksiyon():
    hatali_fonksiyon()

try:
    cagiran_fonksiyon()
except ZeroDivisionError:
    # Traceback'i string olarak al
    tb_str = traceback.format_exc()
    print("Yakalanan hata:")
    print(tb_str)

Çıktı:

Yakalanan hata:
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    cagiran_fonksiyon()
  File "main.py", line 6, in cagiran_fonksiyon
    hatali_fonksiyon()
  File "main.py", line 3, in hatali_fonksiyon
    return 1 / 0
ZeroDivisionError: division by zero

traceback ile Dosyaya Yazma

import traceback

try:
    riskli_islem()
except Exception:
    with open("hata_log.txt", "a") as f:
        traceback.print_exc(file=f)

Kullanışlı traceback Fonksiyonları

import traceback
import sys

try:
    1 / 0
except ZeroDivisionError:
    # Mevcut exception bilgisini al
    exc_type, exc_value, exc_tb = sys.exc_info()
    
    # Sadece stack frame'leri al
    frames = traceback.extract_tb(exc_tb)
    for frame in frames:
        print(f"Dosya: {frame.filename}, Satır: {frame.lineno}, "
              f"Fonksiyon: {frame.name}")
    
    # Formatlanmış traceback string listesi
    lines = traceback.format_exception(exc_type, exc_value, exc_tb)
    print("".join(lines))

Production'da genellikle logging + exc_info=True kullanırsın. traceback modülü daha düşük seviyeli kontrol gerektiğinde işe yarar.


warnings Modülü

Bazen bir hata fırlatmak yerine sadece uyarı vermek istersin. warnings modülü tam olarak bunu yapar — program çalışmaya devam eder ama kullanıcıyı bilgilendirir.

import warnings

def eski_fonksiyon():
    warnings.warn(
        "eski_fonksiyon() kullanımdan kaldırılacak, "
        "yeni_fonksiyon() kullanın.",
        DeprecationWarning,
        stacklevel=2
    )
    # Eski implementasyon...
    return "eski sonuç"

# Kullanım
sonuc = eski_fonksiyon()
# UserWarning: eski_fonksiyon() kullanımdan kaldırılacak...
print(sonuc)  # "eski sonuç" — program devam eder

Yaygın Warning Türleri

import warnings

# DeprecationWarning — Kullanımdan kaldırılacak özellik
warnings.warn("Bu API v3'te kaldırılacak", DeprecationWarning)

# FutureWarning — Gelecekte davranış değişecek
warnings.warn("Bu parametre v2'de zorunlu olacak", FutureWarning)

# UserWarning — Genel uyarı (varsayılan)
warnings.warn("Yapılandırma dosyası bulunamadı, varsayılan kullanılıyor")

# RuntimeWarning — Çalışma zamanı uyarısı
warnings.warn("Büyük dosya, işlem uzun sürebilir", RuntimeWarning)

Warning Filtreleme

import warnings

# Tüm uyarıları göster
warnings.filterwarnings("always")

# Belirli uyarıları gizle
warnings.filterwarnings("ignore", category=DeprecationWarning)

# Uyarıyı hataya çevir
warnings.filterwarnings("error", category=UserWarning)

try:
    warnings.warn("Bu artık hata!", UserWarning)
except UserWarning as e:
    print(f"Uyarı hataya dönüştü: {e}")

warnings vs Exception

Ne zaman hangisi?

DurumKullan
Program devam edemezraise Exception
Girdi geçersizraise ValueError
API değişecekwarnings.warn(DeprecationWarning)
Potansiyel sorun ama program çalışabilirwarnings.warn(UserWarning)
Performans uyarısıwarnings.warn(RuntimeWarning)

⚠️ Dikkat: DeprecationWarning Python 3.2'den itibaren varsayılan olarak gizlenir (sadece test framework'leri gösterir). FutureWarning ise her zaman gösterilir. Kütüphane geliştirirken hangi warning türünü kullandığına dikkat et.


Gerçek Dünya Örneği: Yapılandırma Yöneticisi

Öğrendiklerimizi birleştiren kapsamlı bir örnek yapalım:

import json
import logging
import warnings
from pathlib import Path

# Logger
logger = logging.getLogger(__name__)

# Custom Exception'lar
class YapilandirmaHatasi(Exception):
    """Yapılandırma ile ilgili temel hata."""
    pass

class DosyaOkumaHatasi(YapilandirmaHatasi):
    """Yapılandırma dosyası okunamadığında."""
    pass

class GecersizYapilandirma(YapilandirmaHatasi):
    """Yapılandırma formatı geçersiz olduğunda."""
    def __init__(self, dosya, alan, mesaj):
        self.dosya = dosya
        self.alan = alan
        super().__init__(f"{dosya}: '{alan}' — {mesaj}")

# Yapılandırma Yöneticisi
class YapilandirmaYoneticisi:
    ZORUNLU_ALANLAR = ["veritabani", "port", "host"]
    
    def __init__(self, dosya_yolu):
        self.dosya_yolu = Path(dosya_yolu)
        self.yapilandirma = {}
    
    def yukle(self):
        """Yapılandırma dosyasını yükler."""
        try:
            with open(self.dosya_yolu) as f:
                self.yapilandirma = json.load(f)
        except FileNotFoundError:
            raise DosyaOkumaHatasi(
                f"Dosya bulunamadı: {self.dosya_yolu}"
            )
        except json.JSONDecodeError as e:
            raise GecersizYapilandirma(
                str(self.dosya_yolu), "JSON", f"Parse hatası: {e}"
            ) from e
        
        self._dogrula()
        logger.info(f"Yapılandırma yüklendi: {self.dosya_yolu}")
    
    def _dogrula(self):
        """Zorunlu alanları kontrol eder."""
        for alan in self.ZORUNLU_ALANLAR:
            if alan not in self.yapilandirma:
                raise GecersizYapilandirma(
                    str(self.dosya_yolu), alan, "Zorunlu alan eksik"
                )
        
        # Deprecated alan kontrolü
        if "eski_parametre" in self.yapilandirma:
            warnings.warn(
                "'eski_parametre' v3.0'da kaldırılacak, "
                "'yeni_parametre' kullanın.",
                DeprecationWarning,
                stacklevel=3
            )
    
    def al(self, anahtar, varsayilan=None):
        """Yapılandırma değerini döndürür."""
        return self.yapilandirma.get(anahtar, varsayilan)

Kullanımı:

# Ana uygulama
def uygulamayi_baslat():
    yapilandirma = YapilandirmaYoneticisi("config.json")
    
    try:
        yapilandirma.yukle()
    except DosyaOkumaHatasi:
        logger.error("Yapılandırma dosyası bulunamadı!")
        print("Hata: config.json dosyasını oluşturun.")
        return
    except GecersizYapilandirma as e:
        logger.error(f"Geçersiz yapılandırma: {e}")
        print(f"Yapılandırma hatası: {e}")
        return
    
    host = yapilandirma.al("host")
    port = yapilandirma.al("port")
    print(f"Sunucu başlatılıyor: {host}:{port}")

uygulamayi_baslat()

Bu örnekte:

  • Custom exception hiyerarşisi (YapilandirmaHatasi → alt sınıflar)

  • Exception chaining (from e)

  • warnings kullanımı (deprecated alan)

  • logging ile hata kaydetme

  • Spesifik exception yakalama


Exception ile Context Manager

__enter__ ve __exit__ metodlarıyla kendi context manager'ını yazabilirsin. __exit__ metodu exception bilgisini alır:

class ZamanOlcer:
    """İşlem süresini ölçen context manager."""
    
    def __enter__(self):
        import time
        self.baslangic = time.time()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        self.sure = time.time() - self.baslangic
        
        if exc_type is not None:
            print(f"Hata oluştu ({self.sure:.2f}s): {exc_val}")
            # False dönerse exception tekrar fırlatılır
            # True dönerse exception yutulur
            return False
        
        print(f"İşlem tamamlandı: {self.sure:.2f}s")
        return False

# Kullanım
with ZamanOlcer():
    # Uzun süren işlem
    toplam = sum(range(10_000_000))
    print(f"Toplam: {toplam}")

__exit__ metodunun parametreleri:

  • exc_type: Exception sınıfı (hata yoksa None)

  • exc_val: Exception nesnesi (hata yoksa None)

  • exc_tb: Traceback nesnesi (hata yoksa None)

  • Dönüş değeri True ise exception yutulur, False ise tekrar fırlatılır


assert ile Savunmacı Programlama

assert ifadesi bir koşulun doğru olduğunu garanti eder. Koşul yanlışsa AssertionError fırlatır:

def ortalama_hesapla(sayilar):
    assert len(sayilar) > 0, "Liste boş olamaz!"
    assert all(isinstance(s, (int, float)) for s in sayilar), \
        "Tüm elemanlar sayısal olmalı!"
    return sum(sayilar) / len(sayilar)

print(ortalama_hesapla([10, 20, 30]))  # 20.0
print(ortalama_hesapla([]))             # AssertionError: Liste boş olamaz!

Ama dikkat: assert ifadesi production'da devre dışı bırakılabilir (python -O flag'i ile). Bu yüzden assert'ı input doğrulama için değil, geliştirme sırasında varsayımlarını test etmek için kullan.

# DOĞRU — İç tutarlılık kontrolü
def _dahili_hesaplama(veri):
    sonuc = karmasik_islem(veri)
    assert sonuc >= 0, f"Sonuç negatif olamaz: {sonuc}"  # Bug kontrolü
    return sonuc

# YANLIŞ — Kullanıcı girdisi doğrulama
def kayit_ol(email):
    assert "@" in email  # YAPMA! -O ile devre dışı kalır
    # Bunun yerine:
    if "@" not in email:
        raise ValueError("Geçersiz email adresi")

💡 İpucu: assert'ı "bu hiçbir zaman false olmamalı, eğer oluyorsa kodum buglu" durumları için kullan. Kullanıcı girdisi doğrulama için raise ValueError kullan.


Exception Anti-Pattern'leri Tekrar

Son olarak kaçınman gereken pattern'leri özetleyelim:

# 1. Pokemon Exception Handling — "Hepsini yakala!"
try:
    # ... bir sürü kod ...
    pass
except Exception:
    pass  # 🙈

# 2. Exception'ı string ile kontrol etme
try:
    islem()
except Exception as e:
    if "dosya" in str(e):  # KÖTÜ — kırılgan, dile bağımlı
        pass

# 3. Çok derin try-except iç içe
try:
    try:
        try:
            islem()
        except A:
            pass
    except B:
        pass
except C:
    pass  # Okunabilirlik sıfır

# 4. return finally'de
def kotu_ornek():
    try:
        return 1
    finally:
        return 2  # 1'i ezer! Asla yapma.

Bunlar yerine temiz, okunaklı, spesifik exception handling yaz. Kodunun gelecekteki okuyucusu (muhtemelen sen) teşekkür edecek.


Özet

Bu derste custom exception'lar ve ileri hata yönetimi tekniklerini öğrendik:

  • Custom exception tanımlamak class MyError(Exception): pass kadar basit. Exception hiyerarşisi oluşturarak farklı seviyelerde yakalama yapabilirsin.

  • Exception'a ekstra veri eklemek için __init__ metodunu override et ve super().__init__(mesaj) çağırmayı unutma.

  • Exception chaining (raise ... from ...) orijinal hatanın kaybolmasını engeller. from None ise zinciri bilinçli olarak kırar.

  • EAFP (dene-yakala) Python'ın tercih ettiği stildir — LBYL (önce kontrol et) yerine EAFP kullanmak genellikle daha Pythonic ve güvenlidir.

  • logging modülü production'da hata kaydetmenin doğru yolu. print() yerine logger.error() kullan, exc_info=True ile traceback'i de logla.

  • warnings modülü programı durdurmadan uyarı verir. DeprecationWarning ile eski API'leri kaldırmadan önce kullanıcıları bilgilendir.