Temeller

Python'da istisnalar: try, except, else, finally, raise ve kendi istisnalarınız

Güncellendi: · Skillaria.Top

Python'da istisna, hata durumunu bildiren bir nesnedir ve programın normal akışını keser; try/except bloğu tarafından yakalanmazsa program traceback yazdırıp durur. else yalnızca hata olmadığında, finally ise her durumda çalışır. İstisnayı raise ile kendiniz fırlatabilirsiniz, örneğin raise ValueError("tutar negatif"); kendi istisnanızı Exception sınıfından türeterek oluşturursunuz.

Python'da istisna (exception), bir hatayı bildiren ve normal program akışını durduran nesnedir. Hiçbir yerde yakalanmazsa Python traceback yazdırır ve programı sonlandırır. İstisnaları try/except yapısıyla yakalar, raise komutuyla kendiniz fırlatırsınız. Bu yazıda mekanizmanın nasıl işlediğini, en sık karşılaşılan hataları ve hata yönetiminin hataları gizlemeye dönüşmemesi için nelere dikkat edilmesi gerektiğini inceliyoruz.

Python'da istisna nedir

Bir işlem gerçekleşemediğinde (sıfıra bölme, olmayan bir anahtara erişme, bir metni sayıya çevirme) Python bir istisna nesnesi oluşturur ve onu "fırlatır". Mevcut fonksiyonun çalışması durur, istisna çağrı zinciri boyunca yukarı doğru iletilir ve uygun bir işleyici bulunana kadar devam eder. Bulunamazsa program çöker.

>>> 10 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Sık sorulan bir soruya da yanıt verelim: Python'da sıfıra bölmede ZeroDivisionError istisnası oluşur. Bu, /, // ve % için geçerlidir.

Traceback nasıl okunur

Traceback, programın hataya nasıl ulaştığını gösteren bir rapordur. Alttan yukarıya okumak daha kolaydır. Son satır en önemlisidir: istisna türü ve mesajı. Onun üstünde hatanın olduğu kod satırı, daha yukarıda ise o satırı çağıran kod vardır; giriş noktasına kadar bu böyle sürer.

Traceback (most recent call last):
  File "tracker.py", line 9, in <module>
    total = parse_amount(row)
  File "tracker.py", line 4, in parse_amount
    return float(raw.strip())
           ~~~~~^^^^^^^^^^^^^
ValueError: could not convert string to float: 'yüz'

Python 3.11'den itibaren hatalı satırın altına ~~~^^^ işaretleri çizilir (PEP 657); hangi ifadenin bozulduğunu gösterirler. Tek satırda birden çok çağrı varsa bu özellikle yardımcıdır.

"Did you mean" önerileri

Python 3.10'dan beri yazım hatalı bir adda yakın bir alternatifi önerir. Python 3.15'te bu öneriler daha akıllı hâle geldi: diğer dillerdeki alışkanlıkları da tanır. list.push çağrısı append'i, "kahve".toUpperCase() ise upper'ı önerir. Öneri iç içe alanlara da bakar: sözlük bir nesnenin totals özniteliğinde duruyorsa ve nesnenin kendisinde items() çağırırsanız, .totals.items önerilir. delattr() da yakın adları önerir.

Python istisna türleri

Onlarca yerleşik istisna vardır, ama başlangıçta en çok şunlarla karşılaşırsınız:

İstisnaNe zaman oluşurÖrnek
NameErrorAd tanımlı değil; çoğunlukla yazım hatasıprint(toplm)
TypeErrorİşlem bu tür için uygun değil"5" + 3
ValueErrorTür doğru, değer yanlışint("yüz")
KeyErrorSözlükte böyle bir anahtar yokd["yok"]
IndexErrorİndeks liste sınırlarının dışında[1, 2][5]
AttributeErrorNesnenin böyle bir metodu ya da alanı yok[].push(1)
ZeroDivisionErrorSıfıra bölme1 / 0
FileNotFoundErrorDosya bulunamadıopen("yok.txt")

SyntaxError (ve onun özel hâli IndentationError) ayrıdır: dosya çalışmadan önce, ayrıştırma sırasında oluşur. Bu yüzden aynı dosyada try ile yakalanamaz.

İstisna hiyerarşisi

İstisnalar sınıftır ve bir hiyerarşi oluşturur. En üstte BaseException bulunur. Ondan Exception (neredeyse tüm "normal" hatalar) ve birkaç sistem istisnası türer: KeyboardInterrupt, SystemExit, GeneratorExit.

BaseException
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    └── ZeroDivisionError
      ├── LookupError
      │    ├── KeyError
      │    └── IndexError
      ├── OSError
      │    └── FileNotFoundError
      ├── TypeError
      └── ValueError

Hiyerarşi yakalama sırasında önemlidir: except LookupError, hem KeyError hem IndexError'ı yakalar. KeyboardInterrupt ise bir kod hatası değildir: kullanıcının Ctrl+C'ye bastığını gösterir. Exception'dan bilerek türetilmemiştir; böylece except Exception programın durdurulmasını engellemez.

Hata yönetimi: try/except/else/finally

Hata verebilecek kod try bloğuna yazılır. except bloğunda hangi hatanın yakalanacağı ve ne yapılacağı belirtilir. Python'da istisnaları yönetmek için kullanılan blok tam olarak budur.

def parse_amount(raw):
    try:
        value = float(raw)
    except ValueError as e:
        print(f"Satır atlanıyor: {e}")
        return None
    else:
        return value          # hata olmadıysa çalışır
    finally:
        print("ayrıştırma tamamlandı")   # her durumda çalışır

print(parse_amount("250"))

Buradaki çıktı önce "ayrıştırma tamamlandı", sonra 250.0 olur. else bloğundaki return değeri hatırlar; Python sonucu döndürmeden önce mutlaka finally'yi çalıştırır. Bu yüzden finally temizlik için güvenilirdir: dosyayı kapatmak, bağlantıyı bırakmak.

finally içinde kendi return'ünüzü yazarsanız, bu öncekini geçersiz kılar ve istisnayı bile yutabilir. Python 3.14'ten itibaren böyle kodlar için SyntaxWarning verilir (PEP 765); finally içinde return yazmayın.

Birden fazla istisnayı birlikte yakalamak

Farklı türler için birden fazla except bloğu yazabilir ya da türleri bir demet içinde verebilirsiniz:

try:
    amount = int(data["amount"])
except KeyError:
    amount = 0
except (ValueError, TypeError) as e:
    print("Hatalı tutar:", e)
    amount = 0

Bloklar yukarıdan aşağıya denetlenir ve ilk uyan çalışır; bu yüzden daha özel türleri genel olanların üstüne koyun. Python 3.14'te (PEP 758) birden fazla tür parantezsiz de yazılabilir: except ValueError, TypeError:; ancak as kullanıyorsanız parantez hâlâ zorunludur.

Hata mesajını ve türünü nasıl öğrenirsiniz

İstisna nesnesine as e ile erişilir. str(e) mesajı, type(e).__name__ ise tür adını verir:

try:
    int("yüz")
except ValueError as e:
    print(type(e).__name__)   # ValueError
    print(e)                  # invalid literal for int() with base 10: 'yüz'

traceback modülü: tam hata metni

Bazen hatayı yakalayıp tam traceback'i bir günlük ya da rapor için saklamak istersiniz. Bunun için traceback modülü vardır:

import traceback

try:
    1 / 0
except ZeroDivisionError:
    traceback.print_exc()             # traceback'i stderr'e yazar
    text = traceback.format_exc()     # aynı metin, bir string olarak

logging modülüyle günlük tutuyorsanız, except içinde logging.exception("hesaplama başarısız") çağırmak daha pratiktir: traceback mesaja otomatik eklenir.

Python raise: istisnayı kendiniz fırlatmak

raise komutu bir istisnayı elle fırlatır. Bir fonksiyon, işi bu veriyle yapamayacağını bu şekilde bildirir:

def add_expense(amount):
    if amount < 0:
        raise ValueError(f"Tutar negatif olamaz: {amount}")
    ...

add_expense(-5)
# ValueError: Tutar negatif olamaz: -5

raise bir fonksiyon değil bir komuttur; bu yüzden ardından parantez gerekmez. Parantez içine yazılanlar istisna sınıfının kurucusuna verilen argümanlardır. Türü anlama göre seçin: yanlış değer için ValueError, yanlış tür için TypeError.

Argümansız raise ve raise from

except bloğu içinde argümansız raise, mevcut istisnayı hiç değiştirmeden yeniden fırlatır. Bir günlük kaydı tutup işlemeyi üst katmana bırakmak istediğinizde kullanılır.

Bir istisnayı başka bir istisnayla değiştiriyorsanız raise ... from e kullanın: asıl hata zincirde korunur ve traceback'te ikisi de görünür:

def load_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError as e:
        raise RuntimeError(f"{path} yapılandırma dosyası yok") from e

add_note: hataya bağlam eklemek

Python 3.11'den itibaren (PEP 678) bir istisnaya add_note() ile not ekleyip yeniden fırlatabilirsiniz. İstisnanın türü değişmez; not mesajın altında yazdırılır:

def parse(raw, line_no):
    try:
        return float(raw)
    except ValueError as e:
        e.add_note(f"dosya expenses.csv, satır {line_no}")
        raise

parse("yüz", 17)
# ValueError: could not convert string to float: 'yüz'
# dosya expenses.csv, satır 17

Kendi istisnanızı oluşturmak

Kendi istisnalarınız, alana özgü hatalar içindir: bütçe aşıldı, sipariş zaten ödendi, kullanıcı engellendi. Bunun için Exception'dan türeyen bir sınıf yeterlidir:

class BudgetExceeded(Exception):
    def __init__(self, category, limit, spent):
        super().__init__(f"{category}: limit {limit}, harcanan {spent}")
        self.category = category
        self.limit = limit
        self.spent = spent

def check_budget(category, limit, spent):
    if spent > limit:
        raise BudgetExceeded(category, limit, spent)

try:
    check_budget("Yiyecek", 10000, 12500)
except BudgetExceeded as e:
    print("Uyarı:", e)   # Uyarı: Yiyecek: limit 10000, harcanan 12500

Artık çağıran kod tam olarak bütçe aşımını yakalar ve bunu yazım hatalarıyla karıştırmaz. Büyük projelerde ortak bir taban sınıf (class TrackerError(Exception)) tanımlayıp tüm istisnaları ondan türetmek iyidir; böylece hepsi tek bir except TrackerError ile yakalanabilir. BaseException'dan değil, Exception'dan türetin.

İstisna grupları ve except*

Bazen aynı anda birden fazla hata oluşur: bir dosyanın tamamı kontrol edilirken ya da asyncio.TaskGroup ile paralel görevlerde. Python 3.11'den itibaren bunları ExceptionGroup içinde toplayabilir, türlerine göre except* ile işleyebilirsiniz:

def check_all(rows):
    errors = []
    for r in rows:
        try:
            float(r)
        except ValueError as e:
            errors.append(e)
    if errors:
        raise ExceptionGroup("dosyadaki hatalar", errors)

try:
    check_all(["1", "a", "b"])
except* ValueError as eg:
    print(f"Hatalı satır sayısı: {len(eg.exceptions)}")   # Hatalı satır sayısı: 2

"Her hatayı yakala" alışkanlığı neden kötüdür

En zararlı alışkanlık her şeyi yakalamaktır: except: pass. Program çökmez, ama sorunları da bildirmez. Çıplak except, KeyboardInterrupt ve SystemExit dahil her şeyi yakalar; bu yüzden program Ctrl+C ile durdurulamaz. except Exception: pass biraz daha iyidir, ama yine de isim yazım hatalarını ve mantık hatalarını sessizce yutar.

İstisnaları doğru yönetmek için:

  • yalnızca başa çıkabildiğiniz türü yakalayın: except ValueError, hepsini değil;
  • try bloğunu kısa tutun: yalnızca tam o şekilde bozulabilecek satırları koyun;
  • programın en üst katmanında geniş yakalama (except Exception) yapıyorsanız, traceback'i mutlaka günlüğe yazın;
  • basit dallanma için istisna kullanmayın: uygun yerlerde try/except KeyError yerine dict.get() yeter;
  • kaynaklar için with kullanın: dosyayı hata olsa bile kendisi kapatır.

Özet

Python'da istisnalar hataları bildirmenin normal bir yoludur; gizlemek için değil. Traceback'i alttan yukarıya okuyun, try/except ile belirli türleri yakalayın, başarı durumunda else, temizlik için finally kullanın. raise ile hatayı kendiniz bildirirsiniz, raise from ve add_note bağlamı korur, kendi istisnalarınız Exception'dan türetilir. Kursta bu konu uygulamalı işlenir: harcama takipçisi, hatalı bir CSV satırı yüzünden çökmez ve bütçe aşımını kendi istisnasıyla bildirir.

Sık sorulan sorular

Hata ile istisna arasında fark var mı?

Günlük dilde eş anlamlıdırlar. Teknik olarak SyntaxError kod ayrıştırılırken oluşur; diğer hatalar çalışma zamanında istisna nesnesi olarak doğar ve yakalanabilir.

İstisnadan sonra aynı satırdan devam edebilir miyim?

Hayır. Yakalamadan sonra çalışma try/except bloğunun ardından devam eder, hatanın oluştuğu satırdan değil. Dosyanın her satırını işlemek istiyorsanız try bloğunu döngünün içine koyun.

except bloğunun içinde bir istisna oluşursa ne olur?

Yeni istisna yukarı iletilir ve özgün istisna __context__ özniteliğinde saklanır. Traceback'te "During handling of the above exception, another exception occurred" ifadesi görünür.

Testte bir fonksiyonun istisna fırlattığını nasıl doğrularım?

pytest'te with pytest.raises(ValueError): kullanılır; unittest'te self.assertRaises(ValueError). Test, bloğun içinde doğru türde bir istisna oluşursa geçer.

assert nedir ve raise'den farkı ne?

assert koşul, "mesaj" biçiminde koşul yanlışsa AssertionError fırlatır. Geliştirme sırasındaki kontroller içindir: -O bayrağıyla çalıştırıldığında assert'ler devre dışı kalır; kullanıcı girdisini kontrol etmek için if ve raise kullanmalısınız.

Bu konuyu videoyla öğrenin

Bu konu “Python 3.15: от новичка до профи” kursunda uygulamalı olarak ayrıntılı işleniyor.

Kursa git