Python'da bir istisnayı bilerek oluşturmak için raise deyimini kullanırsınız: raise ValueError("yaş negatif olamaz") yazdığınız anda çalışma durur ve istisna, onu yakalayacak bir except bulunana kadar çağrı yığınında yukarı çıkar. Kendi istisna türünüzü tanımlamak için Exception sınıfından türeyen bir sınıf yazmanız yeterlidir. Bir hatayı başka bir hataya dönüştürürken raise YeniHata(...) from eski yazımı, iki hatanın arasındaki neden sonuç ilişkisini traceback'te açıkça gösterir.

Bu yazıda raise deyiminin tüm biçimlerini, argümansız raise ile yeniden fırlatmayı, örtük ve açık istisna zincirlemeyi, from None ile zinciri gizlemeyi, kendi istisna hiyerarşinizi kurmayı, add_note() ile hataya not eklemeyi ve birden fazla hatayı birlikte bildiren ExceptionGroup ile except* yapısını Python 3.15.2'de çalıştırılmış örneklerle anlatıyoruz. Hataları yakalamanın temeli olan try, except, else ve finally için önce Python try except rehberini okumanızı öneririz. Tüm konu listesi Python öğrenme rehberi sayfasındadır.

raise deyiminin temel kullanımı

raise deyiminin temel kullanımı: Argümanın türü yanlış, Türü doğru, değeri geçersiz, Sözlükte anahtar yok, Sıra numarası aralık dışında

raise deyiminden sonra ya bir istisna örneği (instance) ya da bir istisna sınıfı yazılır. Bu sınıf BaseException'dan türemiş olmalıdır; pratikte neredeyse her zaman Exception veya onun bir alt sınıfıdır. Fonksiyonlarınızda geçersiz girdiyi reddetmenin en temiz yolu, durumu en iyi anlatan yerleşik türü seçip açıklayıcı bir mesajla fırlatmaktır:

python
def yas_dogrula(yas):
    if not isinstance(yas, int):
        raise TypeError(f"yaş tam sayı olmalı, {type(yas).__name__} verildi")
    if yas < 0:
        raise ValueError("yaş negatif olamaz")
    return yas

for deger in (30, -2, "otuz"):
    try:
        print(yas_dogrula(deger))
    except (TypeError, ValueError) as e:
        print(f"{type(e).__name__}: {e}")
# çıktı: 30
# çıktı: ValueError: yaş negatif olamaz
# çıktı: TypeError: yaş tam sayı olmalı, str verildi

Burada iki farklı yerleşik tür kullandık ve bu bir tercihtir: TypeError değerin türünün yanlış olduğunu, ValueError ise türün doğru ama değerin uygunsuz olduğunu anlatır. Çağıran kod bu ayrım sayesinde iki durumu farklı biçimde ele alabilir. Yerleşik türlerin ne zaman kullanıldığını belgelerdeki Built-in Exceptions sayfası tek tek açıklar; aşağıdaki tablo en sık ihtiyaç duyulanları özetler:

DurumUygun türÖrnek mesaj
Argümanın türü yanlışTypeErroryaş tam sayı olmalı
Türü doğru, değeri geçersizValueErroryaş negatif olamaz
Sözlükte anahtar yokKeyErroranahtarın kendisi
Sıra numarası aralık dışındaIndexErrorlist index out of range
Alt sınıfın yazması gereken metotNotImplementedErroralt sınıf bu metodu tanımlamalı
Hiçbir kategoriye uymayan hataRuntimeErrordurumu anlatan bir cümle

Sınıf adını parantezsiz yazarsanız Python sınıfı argümansız çağırarak bir örnek oluşturur; yani raise ValueError ile raise ValueError() aynıdır. Bu durumda mesaj boş kalır, bu yüzden kullanıcıya gösterilecek hatalarda bir açıklama vermek daha yararlıdır:

python
try:
    raise ValueError
except ValueError as e:
    print(repr(e), repr(str(e)))
# çıktı: ValueError() ''

Argümansız raise: yakaladığınız hatayı yeniden fırlatmak

Bir except bloğunun içinde tek başına raise yazarsanız, o an işlenmekte olan istisna (belgelerdeki adıyla etkin istisna) olduğu gibi, orijinal traceback'iyle birlikte yeniden fırlatılır. Bu desen, hatayı kaydetmek ya da kısmi bir temizlik yapmak isteyip asıl kararı üst katmana bırakmak istediğinizde kullanılır:

python
def ayar_oku(ayarlar):
    try:
        return ayarlar["port"]
    except KeyError:
        print("Hata kaydedildi, yeniden fırlatılıyor")
        raise

try:
    ayar_oku({})
except KeyError as e:
    print("Üst katman yakaladı:", repr(e))
# çıktı: Hata kaydedildi, yeniden fırlatılıyor
# çıktı: Üst katman yakaladı: KeyError('port')

raise e yazmak da çalışır, ancak argümansız raise niyetinizi daha açık anlatır ve yeni bir satır numarası eklemez. Etkin bir istisna yokken argümansız raise çalıştırırsanız dil kuralına göre bir RuntimeError oluşur:

python
try:
    raise
except RuntimeError as e:
    print(e)
# çıktı: No active exception to reraise

Örtük zincirleme: hatayı işlerken çıkan yeni hata

Bir except veya finally bloğunun içinde yeni bir istisna oluşursa Python, işlenmekte olan eski istisnayı yeni istisnanın __context__ özelliğine otomatik olarak bağlar. Yakalanmayan yeni hata ekrana basılırken iki traceback birden görünür ve aralarında "During handling of the above exception, another exception occurred" cümlesi yer alır:

python
ayarlar = {}
try:
    port = ayarlar["port"]
except KeyError:
    raise RuntimeError("ayar dosyasında port yok")
# hata: RuntimeError: ayar dosyasında port yok
text
Traceback (most recent call last):
  File "ornek.py", line 3, in <module>
    port = ayarlar["port"]
           ~~~~~~~^^^^^^^^
KeyError: 'port'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "ornek.py", line 5, in <module>
    raise RuntimeError("ayar dosyasında port yok")
RuntimeError: ayar dosyasında port yok

Bu çıktı okuyana şunu söyler: ikinci hata, birincisi işlenirken beklenmedik biçimde ortaya çıktı. Oysa buradaki RuntimeError bir kaza değil, bilinçli bir dönüştürmedir. Bu ilişkiyi doğru anlatmak için açık zincirleme kullanılır.

raise from: açık zincirleme

raise from: açık zincirleme: except içinde raise Yeni(), raise Yeni() from eski, raise Yeni() from None

raise YeniHata(...) from eski_hata yazdığınızda eski istisna yeni istisnanın __cause__ özelliğine atanır ve traceback'teki ara cümle "The above exception was the direct cause of the following exception" olur. Bu, alt katmandaki teknik bir hatayı (dosya yok, anahtar yok) üst katmanın anlayacağı bir hataya çevirirken kullanılan standart yoldur:

python
def port_al(ayarlar):
    try:
        return int(ayarlar["port"])
    except KeyError as e:
        raise RuntimeError("ayar dosyasında port yok") from e

port_al({})
# hata: RuntimeError: ayar dosyasında port yok
text
Traceback (most recent call last):
  File "ornek.py", line 3, in port_al
    return int(ayarlar["port"])
               ~~~~~~~^^^^^^^^
KeyError: 'port'

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

Traceback (most recent call last):
  File "ornek.py", line 7, in <module>
    port_al({})
    ~~~~~~~^^^^
  File "ornek.py", line 5, in port_al
    raise RuntimeError("ayar dosyasında port yok") from e
RuntimeError: ayar dosyasında port yok

from sonrasında bir istisna örneği ya da None yazılabilir. from None, eski istisnayı ekran çıktısından gizler: Python __suppress_context__ özelliğini True yapar ve traceback'te yalnızca yeni hata görünür. Eski hata yine de __context__ içinde saklanır, yani hata ayıklarken ona ulaşabilirsiniz. Belgeler bunu örneğin bir KeyError'ı AttributeError'a çeviren sınıflarda kullanışlı bulur:

python
class Ayarlar:
    def __init__(self, veri):
        self._veri = veri

    def __getattr__(self, ad):
        try:
            return self._veri[ad]
        except KeyError:
            raise AttributeError(f"{ad!r} adlı ayar yok") from None

a = Ayarlar({"tema": "koyu"})
print(a.tema)
try:
    a.dil
except AttributeError as e:
    print(e)
    print(repr(e.__cause__), repr(e.__context__), e.__suppress_context__)
# çıktı: koyu
# çıktı: 'dil' adlı ayar yok
# çıktı: None KeyError('dil') True

Üç özelliğin ilişkisi şöyledir: __context__ her zaman otomatik dolar; __cause__ yalnızca from ile dolar; __suppress_context__ ise from kullanıldığında True olur. Varsayılan traceback gösterimi __cause__ varsa onu her zaman gösterir, __context__'i ise yalnızca __cause__ boşsa ve gizleme bayrağı kapalıysa gösterir.

Yazım__cause____suppress_context__Traceback'te görünen
except içinde raise Yeni()NoneFalseİki hata, "During handling..." ile
raise Yeni() from eskieskiTrueİki hata, "direct cause" ile
raise Yeni() from NoneNoneTrueYalnızca yeni hata

Kendi istisna sınıfınızı tanımlamak

Belgeler kullanıcı tanımlı istisnaların doğrudan ya da dolaylı olarak Exception'dan türetilmesini, BaseException'dan türetilmemesini önerir. İsimlerin standart istisnalar gibi Error ile bitmesi yaygın bir gelenektir. Bir uygulama ya da kütüphane yazıyorsanız önce tek bir temel sınıf, sonra ondan türeyen özel sınıflar tanımlamak işe yarar: çağıran kod isterse yalnızca temel sınıfı yakalayarak sizin tüm hatalarınızı tek seferde ele alabilir.

python
class UygulamaHatasi(Exception):
    """Bu uygulamadaki tüm hataların atası."""

class StokHatasi(UygulamaHatasi):
    def __init__(self, urun, istenen, mevcut):
        super().__init__(f"{urun}: {istenen} adet istendi, stokta {mevcut} var")
        self.urun = urun
        self.istenen = istenen
        self.mevcut = mevcut

class OdemeHatasi(UygulamaHatasi):
    pass

def siparis_ver(urun, adet, stok):
    mevcut = stok.get(urun, 0)
    if adet > mevcut:
        raise StokHatasi(urun, adet, mevcut)
    return "onaylandı"

stok = {"defter": 3}
try:
    siparis_ver("defter", 5, stok)
except StokHatasi as e:
    print(e)
    print("Eksik:", e.istenen - e.mevcut)

try:
    raise OdemeHatasi("kart reddedildi")
except UygulamaHatasi as e:
    print(type(e).__name__, "-", e)
# çıktı: defter: 5 adet istendi, stokta 3 var
# çıktı: Eksik: 2
# çıktı: OdemeHatasi - kart reddedildi

Bu örnekte iki ayrıntı önemlidir. Birincisi, StokHatasi sorunla ilgili verileri (urun, istenen, mevcut) özellik olarak taşır; böylece onu yakalayan kod mesajı ayrıştırmak zorunda kalmaz. Belgeler de istisna sınıflarını basit tutmayı, genellikle yalnızca bu tür bilgi taşıyan özellikler eklemeyi önerir. İkincisi, __init__ içinde super().__init__(mesaj) çağrısı yapılmıştır. Bu çağrıyı unutursanız args demeti yapıcıya verdiğiniz ham argümanlarla dolar ve str(e) anlamsız bir demet metni döndürür:

python
class EksikHata(Exception):
    def __init__(self, urun, adet):
        self.urun = urun
        self.adet = adet

e = EksikHata("defter", 5)
print(str(e))
print(e.args)
# çıktı: ('defter', 5)
# çıktı: ('defter', 5)

Mesajı tamamen kendiniz biçimlendirmek istiyorsanız __str__ metodunu da ezebilirsiniz. Belgeler ayrıca aynı anda birden fazla yerleşik istisna türünden kalıtım almamanızı önerir: bazı yerleşik türlerin bellek düzeni farklıdır ve args yönetimleri çakışabilir. Sınıf yazımı ve kalıtım konusunda temel bilgiye ihtiyacınız varsa Python class ve nesne ile kalıtım ve super() yazılarına bakabilirsiniz.

add_note() ile hataya not eklemek

Bazen hatanın oluştuğu yer, ona bağlam katabilecek bilgiye sahip değildir. Örneğin int() geçersiz bir metinle karşılaştığını bilir ama bu metnin dosyanın kaçıncı satırında olduğunu bilmez. Python 3.11 ile gelen add_note(not) metodu, yakaladığınız istisnaya sonradan metin eklemenizi sağlar. Notlar __notes__ listesinde saklanır ve standart traceback'te istisna mesajının hemen altında, eklendikleri sırayla gösterilir:

python
def satir_isle(no, metin):
    try:
        return int(metin)
    except ValueError as e:
        e.add_note(f"satır {no} işlenirken oluştu")
        e.add_note("dosya: sayilar.txt")
        raise

try:
    satir_isle(3, "x")
except ValueError as e:
    print(e)
    print(e.__notes__)
# çıktı: invalid literal for int() with base 10: 'x'
# çıktı: ['satır 3 işlenirken oluştu', 'dosya: sayilar.txt']

Not yalnızca metin olabilir; e.add_note(5) çağrısı TypeError: add_note() argument must be str, not int hatası verir. Bu teknik, bir döngüde biriktirdiğiniz hataları sonradan bir grup hâlinde fırlatırken her birine "hangi adımda oluştu" bilgisini eklemek için de kullanılır.

ExceptionGroup: birden fazla hatayı birlikte bildirmek

Normalde bir işlem ilk hatada durur. Ama bir formdaki tüm alanları doğrularken ya da birden fazla görevi paralel çalıştırırken, hepsini toplayıp tek seferde bildirmek daha kullanışlıdır. Python 3.11 ile gelen yerleşik ExceptionGroup(mesaj, istisnalar) bir istisna listesini sarar ve kendisi de bir istisnadır. Listedeki öğeler sınıf değil örnek olmalıdır; ayrıca ExceptionGroup yalnızca Exception alt sınıflarını kabul eder:

python
try:
    ExceptionGroup("g", [KeyboardInterrupt()])
except TypeError as e:
    print(e)
# çıktı: Cannot nest BaseExceptions in an ExceptionGroup

KeyboardInterrupt gibi BaseException türlerini sarmanız gerekiyorsa BaseExceptionGroup kullanılır. Belgelere göre BaseExceptionGroup yapıcısı, içindeki tüm öğeler Exception türündeyse kendiliğinden bir ExceptionGroup döndürür. Aşağıdaki doğrulama fonksiyonu tüm sorunları toplar ve grup hâlinde fırlatır; except* blokları da grubu türlere göre parçalar:

python
def kayit_dogrula(kayit):
    hatalar = []
    if not kayit.get("ad"):
        hatalar.append(ValueError("ad boş"))
    if not isinstance(kayit.get("yas"), int):
        hatalar.append(TypeError("yaş tam sayı değil"))
    if "@" not in kayit.get("eposta", ""):
        hatalar.append(ValueError("e-posta geçersiz"))
    if hatalar:
        raise ExceptionGroup("kayıt geçersiz", hatalar)

try:
    kayit_dogrula({"ad": "", "yas": "yirmi", "eposta": "ali"})
except* ValueError as grup:
    print("Değer hataları:", [str(e) for e in grup.exceptions])
except* TypeError as grup:
    print("Tip hataları:", [str(e) for e in grup.exceptions])
# çıktı: Değer hataları: ['ad boş', 'e-posta geçersiz']
# çıktı: Tip hataları: ['yaş tam sayı değil']

except* normal except'ten farklı çalışır. Her except* bloğu grubu eşleşen ve eşleşmeyen iki alt gruba ayırır; eşleşen alt grup boş değilse blok çalışır ve kalan kısım bir sonraki except*'e geçer. Yani birden fazla except* bloğu aynı anda çalışabilir. Bloğa bağlanan değişken her zaman bir gruptur, tek bir istisna değildir; içindeki öğelere exceptions demeti ile ulaşırsınız. Hiçbir bloğun eşleşmediği hatalar yeni bir grup olarak yukarı fırlatılmaya devam eder.

Dil referansındaki bazı kurallar da bilinmelidir. Aynı try deyiminde except ile except* birlikte kullanılamaz. except* için tür yazmak zorunludur, except*: sözdizimi hatasıdır. except* ExceptionGroup gibi bir grup türüyle eşleştirmek anlamı belirsiz olduğu için TypeError verir. break, continue ve return bir except* bloğunda yer alamaz. Son olarak, try bloğundan grup olmayan tek bir istisna çıkar ve bir except* ile eşleşirse Python onu boş mesajlı bir gruba sarar:

python
try:
    raise BlockingIOError
except* BlockingIOError as e:
    print(repr(e))
# çıktı: ExceptionGroup('', (BlockingIOError(),))

Grubu düz bir except Exception ile de yakalayabilirsiniz; bu durumda grubun tamamı tek bir nesne olarak gelir. Grubu elle parçalamak için subgroup(koşul) ve split(koşul) metotları vardır. Koşul bir tür, bir tür demeti ya da Python 3.13 ile birlikte her istisna için doğru veya yanlış döndüren herhangi bir çağrılabilir olabilir. split() eşleşen ve kalan kısmı bir çift olarak döndürür, boş kalan taraf None olur:

python
grup = ExceptionGroup("kayıt geçersiz",
                      [ValueError("a"), TypeError("b"), ValueError("c")])
print(grup)
eslesen, kalan = grup.split(ValueError)
print(repr(eslesen))
print(repr(kalan))
print(grup.subgroup(KeyError))
print(grup.subgroup(lambda e: str(e) == "b"))
# çıktı: kayıt geçersiz (3 sub-exceptions)
# çıktı: ExceptionGroup('kayıt geçersiz', [ValueError('a'), ValueError('c')])
# çıktı: ExceptionGroup('kayıt geçersiz', [TypeError('b')])
# çıktı: None
# çıktı: kayıt geçersiz (1 sub-exception)

Eşzamanlı programlamada asyncio.TaskGroup başarısız görevlerin hatalarını bu yapıyla bildirir; ayrıntılar için Python asyncio yazısına bakabilirsiniz.

Sürüm notları

Bu yazıdaki özelliklerin bir kısmı yakın sürümlerde geldi. add_note(), __notes__, ExceptionGroup ve except* Python 3.11 ile eklendi. Python 3.13, subgroup() ve split() metotlarının koşul olarak herhangi bir çağrılabilir kabul etmesini sağladı. Python 3.14 ile except ve except* satırlarında as kullanılmadığında parantezsiz çoklu tür yazımı geldi. Python 3.15 ise yeni lazy import özelliğine bağlı olarak ImportError'ın alt sınıfı olan ImportCycleError'ı ekledi; bu hata, ertelenmiş bir içe aktarma doğrudan ya da dolaylı olarak kendisini içe aktarmaya çalıştığında oluşur. Aynı sürümde ImportError ve ModuleNotFoundError nesnelerinin repr() çıktısı, anahtar kelimeyle verilen name ve path değerlerini de göstermeye başladı:

python
print(repr(ImportError("modül yok", name="eklenti", path="/opt/eklenti.py")))
# çıktı: ImportError('modül yok', name='eklenti', path='/opt/eklenti.py')

Python 3.15 ile gelen diğer değişikliklerin listesi Python 3.15 yenilikleri yazısındadır.

Sık yapılan hatalar

Sık yapılan hatalar: Genel Exception fırlatmak, BaseException'dan türetmek, super().__init__() çağırmamak, Dönüştürürken from kullanmamak
  • Genel Exception fırlatmak: raise Exception("hata") çağıranın hatayı türüne göre ayırmasını imkânsız kılar. Uygun yerleşik türü ya da kendi alt sınıfınızı kullanın.
  • BaseException'dan türetmek: Böyle bir sınıf except Exception ile yakalanmaz ve SystemExit gibi davranır. Kendi sınıflarınızı Exception'dan türetin.
  • super().__init__() çağırmamak: str(e) beklediğiniz mesaj yerine ham argüman demetini gösterir.
  • Dönüştürürken from kullanmamak: Traceback hatanın kasıtlı bir dönüştürme değil, ikinci bir kaza olduğunu söyler; okuyan kişi yanlış yöne bakar.
  • from None ile bilgiyi gereksiz gizlemek: Alt katmandaki hata çoğu zaman sorunun kökünü gösterir. Gizlemeyi yalnızca eski hata kullanıcı için anlamsızsa tercih edin.
  • ExceptionGroup'a sınıf vermek: Liste öğeleri ValueError değil, ValueError("...") gibi örnekler olmalıdır.
  • except* değişkenini tek istisna sanmak: except* ValueError as e içindeki e bir gruptur; öğelere e.exceptions ile ulaşın.

Sık Sorulan Sorular

raise ile raise e arasında fark var mı?

İkisi de aynı istisnayı yeniden fırlatır. Argümansız raise yalnızca bir except bloğunun içinde (etkin istisna varken) çalışır ve niyeti en açık anlatan biçimdir. Etkin istisna yokken kullanılırsa RuntimeError: No active exception to reraise alırsınız.

Özel istisna sınıfına neden Error eki koyuluyor?

Bu bir zorunluluk değil, gelenektir. Belgeler çoğu istisnanın standart istisnalar gibi "Error" ile biten isimler aldığını belirtir; bu, sınıfın bir hata türü olduğunu ilk bakışta anlatır.

raise from None ne işe yarar?

Yeni istisnanın traceback'inde eski istisnanın gösterilmesini engeller. Eski istisna silinmez, __context__ özelliğinde durur. Bir iç ayrıntıyı (örneğin bir sözlük anahtarının yokluğunu) kullanıcıya daha anlamlı bir hatayla sunarken kullanılır.

except* ne zaman kullanılır?

Yalnızca ExceptionGroup ile çalışırken: birden fazla doğrulama hatasını toplayan kodlarda ya da paralel görevlerin hatalarını gruplayan asyncio.TaskGroup gibi araçlarda. Tek tek fırlatılan sıradan istisnalar için normal except yeterlidir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Python öğrenme rehberi · Python try except · Python sık hata mesajları · Python class ve nesne · Python kalıtım ve super() · Python asyncio · Python unittest