Python'da istisnalar: try, except, else, finally, raise ve kendi istisnalarınız
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:
| İstisna | Ne zaman oluşur | Örnek |
|---|---|---|
NameError | Ad tanımlı değil; çoğunlukla yazım hatası | print(toplm) |
TypeError | İşlem bu tür için uygun değil | "5" + 3 |
ValueError | Tür doğru, değer yanlış | int("yüz") |
KeyError | Sözlükte böyle bir anahtar yok | d["yok"] |
IndexError | İndeks liste sınırlarının dışında | [1, 2][5] |
AttributeError | Nesnenin böyle bir metodu ya da alanı yok | [].push(1) |
ZeroDivisionError | Sıfıra bölme | 1 / 0 |
FileNotFoundError | Dosya 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; trybloğ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 KeyErroryerinedict.get()yeter; - kaynaklar için
withkullanı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 konu “Python 3.15: от новичка до профи” kursunda uygulamalı olarak ayrıntılı işleniyor.
Kursa git