İstisnalar, modüller ve sistematik hata ayıklama
Bir programın doğru sözdizimine sahip olması, her durumda sorunsuz çalışacağı anlamına gelmez. Dosya bulunamayabilir, metin sayıya dönüştürülemeyebilir, beklenen anahtar sözlükte olmayabilir veya bir hesap sıfıra bölme yapabilir.
Bu hafta istisna (exception) kavramını, try/except ile belirli çalışma zamanı sorunlarını ele almayı, gerektiğinde raise ile açık hata üretmeyi, standart kütüphane modüllerini kullanmayı ve hata ayıklamayı daha sistematik bir sürece dönüştürmeyi öğreneceğiz.
Bu bölümün kapsamı
- söz dizimi, çalışma zamanı ve mantık hatasının ayrımı
try/exceptile belirli istisnayı ele alma ve genişexceptriskiFileNotFoundError,KeyErrorveValueErroreşlemesiraiseile sözleşme ihlalini bildirme veassertten ayırmaNonesinyali veis Nonedenetimi- traceback’te hata türü, mesaj ve kendi kodundaki satır
importile modül kullanımı ve main guard’ın ayırdığı iki durum
- kendi istisna sınıfını tanımlama
loggingmodülü- adım adım çalıştıran hata ayıklayıcı arayüzü
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Program hata vermiyor ama sonuç yanlış; bu ne tür bir hatadır?
Hataların hepsi kırmızı bir mesajla gelmez. Önce hatayı türüne göre ayıracak, sonra beklenen hataları kontrollü biçimde karşılamayı ve bir traceback’i baştan sona okumayı öğreneceğiz.
Hata türlerini ayırmak
Söz dizimi hatası
if puan >= 60
print("Geçti")
İki nokta eksiktir ve SyntaxError oluşur.
Çalışma zamanı hatası
sayi = int("abc") # ValueError
# 10 / 0 # ZeroDivisionError
# [1, 2][5] # IndexErrorKod sözdizimsel olarak geçerlidir; sorun yürütme sırasında ortaya çıkar.
Mantık hatası
Program çalışır fakat yanlış sonuç üretir. Örneğin daire çevresinin formülünü eksik yazalım:
def daire_cevresi(yaricap):
return 3.14 * yaricap # Hatalı: 2 çarpanı eksikDoğru formülü standart kütüphanedeki math.pi ile açıkça yazabiliriz:
import math
def daire_cevresi(yaricap):
return 2 * math.pi * yaricapassert abs(daire_cevresi(1) - 2 * math.pi) < 0.000001Bu tür sorunları Python otomatik olarak her zaman fark edemez; beklenen davranışla karşılaştırmak gerekir.
Hata ayıklamada ilk soru yalnızca “program çalışıyor mu?” değildir. Sorun hangi sınıfa ait ve beklenen davranış neydi? soruları düzeltme yöntemini belirler.
İstisna nedir?
Python çalışırken olağan akışı sürdüremediği bir durumla karşılaştığında istisna oluşturabilir:
int("on")ValueError oluşur. Hata türü ve mesajı yalnızca “program bozuldu” demek değildir; sorunun niteliği hakkında bilgi taşır.
try / except
metin = "42"
try:
sayi = int(metin)
print("Sayı:", sayi)
except ValueError:
print("Geçerli bir tam sayı değil")metin = "abc" olduğunda dönüşüm başarısız olur ve yalnızca ValueError bloğu çalışır.
Hata nesnesinin mesajını okumak: as e
Bazen kullanıcıya göstermek veya hata ayıklarken görmek için Python’ın gerçek istisna mesajını da incelemek isteriz:
metin = "abc"
try:
sayi = int(metin)
except ValueError as e:
print("Hata türü: ValueError")
print("Ayrıntı:", e)Buradaki e, oluşan istisna nesnesine verdiğimiz yerel addır. Örneğin mesaj invalid literal for int() with base 10: 'abc' olabilir.
Kullanıcıya ham hata mesajını her zaman aynen göstermek zorunda değilsiniz. Ancak geliştirme ve hata ayıklama sırasında except ... as e gerçek sorunun ayrıntısını görmeye yardım eder.
Tahmin et — hangi blok çalışır?
Önce "75", sonra "yetmis" ile sonucu tahmin edin.
Alıştırma — dönüşüm hatasını ele alın
Kodu try/except ValueError kullanacak biçimde değiştirin; geçersiz metinde Geçerli sayı değil yazdırılsın.
girdi_metni = "abc"
try:
sayi = int(girdi_metni)
print(sayi)
except ValueError:
print("Geçerli sayı değil")Belirli hata türünü yakalamak
Şu kullanım birçok beklenmeyen problemi sessizce yutabilir:
try:
...
except:
...Yalnızca sayı dönüşüm hatasını yönetiyorsanız:
try:
sayi = int(metin)
except ValueError:
...daha açıklayıcıdır.
“Program hiçbir zaman hata vermesin” diye her şeyi geniş bir except ile kapatmak hata ayıklamayı zorlaştırır. Yalnızca anlamlı biçimde ele alabildiğiniz ve beklediğiniz hata türlerini yakalayın.
Birden fazla hata türü
def bol(metin, bolen):
sayi = int(metin)
return sayi / bolen
try:
sonuc = bol("20", 0)
print(sonuc)
except ValueError:
print("Metin sayıya dönüştürülemedi")
except ZeroDivisionError:
print("Sıfıra bölme yapılamaz")Ayrı hata türleri ayrı nedenleri görünür kılar.
12. haftanın dosya hataları
Geçen hafta “dosya bulunamıyor, sütun yok, dönüşüm bozuk olabilir” demiştik. Şimdi bunları doğrudan ele alalım.
FileNotFoundError
try:
with open("veri.csv", "r", encoding="utf-8") as dosya:
icerik = dosya.read()
except FileNotFoundError as e:
print("Dosya bulunamadı: veri.csv")
print("Ayrıntı:", e)Bu blok yalnızca dosyanın bulunamaması durumunu yönetir. Dosyanın içindeki veri hatalarını aynı başlık altında gizlemez.
KeyError: beklenen sütun/alan yok
DictReader kaydında "puan" sütunu yoksa köşeli parantez erişimi KeyError oluşturabilir:
kayit = {"ad": "Deniz"}
try:
puan = int(kayit["puan"])
except KeyError as e:
print("Beklenen alan bulunamadı:", e)Gerçek CSV örneğinde:
import csv
try:
with open("ogrenciler.csv", "r", encoding="utf-8-sig", newline="") as dosya:
okuyucu = csv.DictReader(dosya, delimiter=";")
for kayit in okuyucu:
puan = int(kayit["puan"])
print(kayit["ad"], puan)
except FileNotFoundError as e:
print("CSV dosyası bulunamadı:", e)
except KeyError as e:
print("CSV sütunu eksik:", e)
except ValueError as e:
print("Sayısal dönüşüm başarısız:", e)Yanlış delimiter kullanırsanız DictReader beklediğiniz sütunları hiç üretmeyebilir ve bu da daha sonra KeyError olarak görünür. Hata türünü yakalamak kadar kök nedeni araştırmak da önemlidir.
İstisna mı, koşul mu?
Puanın 0 ile 100 arasında olması normal bir iş kuralıdır:
if 0 <= puan <= 100:
...Metnin int() ile dönüştürülememesi ise dönüşüm sırasında oluşan bir istisnadır:
try:
puan = int(girdi_metni)
except ValueError:
...raise: fonksiyon sözleşmesi ihlalini açıkça bildirmek
- haftada
assertifadelerini geliştiricinin beklentisini doğrulamak için kullanmış, kullanıcı girdisi yönetiminin aracı olmadığını söylemiştik. Bir fonksiyonun geçersiz argümanı çalışma zamanında açıkça reddetmesi gerekiyorsa istisna üretebilir:
def saat_ucreti(toplam_ucret, saat):
if saat <= 0:
raise ValueError("saat 0'dan büyük olmalı")
return toplam_ucret / saatÇağıran kod isterse bunu yakalar:
try:
print(saat_ucreti(1000, 0))
except ValueError as e:
print("Geçersiz girdi:", e)raise ValueError(...), “bu fonksiyonun sözleşmesine göre verilen değer geçersiz” bilgisini çağıran koda taşır.
Her geçersiz durumda mutlaka raise kullanmak zorunda değilsiniz. Tasarım seçimi, çağıranın hatayla nasıl çalışacağına bağlıdır. Önemli olan fonksiyonun sözleşmesinin açık ve tutarlı olmasıdır.
None ile hata sinyali: kolay ama sorumluluğu çağırana taşır
Şu tasarım geçersiz puanda None döndürüyor:
def puani_coz(metin):
try:
puan = int(metin)
except ValueError:
return None
if 0 <= puan <= 100:
return puan
return NoneAvantajı: çağıran kod sonucu normal bir değer gibi alabilir ve açıkça kontrol edebilir.
Bedeli: her çağıran None durumunu hatırlamak zorundadır.
puan = puani_coz("abc")
# print(puan + 10) # TypeError: None ile int toplanamazHata artık dönüşüm satırında değil, daha ileride ve bağlamı daha belirsiz bir yerde ortaya çıkabilir.
Bu nedenle çağıran kod:
puan = puani_coz("abc")
if puan is None:
print("Puan çözülemedi")
else:
print("Yeni puan:", puan + 10)biçiminde sözleşmeye uymalıdır.
Neden is None?
None Python’da tekil bir özel nesnedir. Geleneksel ve önerilen kontrol biçimi:
if sonuc is None:
...
if sonuc is not None:
...şeklindedir; sonuc == None yazmak yerine is None kullanılır. Burada is, iki ifadenin aynı nesneyi gösterip göstermediğini sınar; None kontrolü bu kullanımın en yaygın örneklerindendir.
- haftadaki bütünleştirici uygulamada
if kayit is None:veassert ... is Nonegöreceksiniz. Bunlar==operatörünün rastgele bir varyasyonu değil,Noneiçin bilinçli kimlik kontrolleridir.
None mı raise mı?
İki tasarım da bağlama göre anlamlı olabilir:
None döndürme
→ geçersizlik beklenen bir sonuç seçeneği olabilir
→ çağıran her seferinde kontrol etmelidir
raise ValueError
→ sözleşme ihlalini akıştan ayırır
→ çağıran isterse uygun sınırda except ile yakalar
Bu derste iki yaklaşımı da tanıyoruz; aynı tür problemler içinde keyfî biçimde karıştırmak yerine sözleşmeyi açık tutuyoruz.
Traceback okumak
Traceback (most recent call last):
File "rapor.py", line 12, in <module>
toplam = fiyat_hesapla("abc")
File "rapor.py", line 5, in fiyat_hesapla
fiyat = float(metin)
ValueError: could not convert string to float: 'abc'
Başlangıçta:
- En alttaki hata türünü bulun.
- Hata mesajını okuyun.
- Kendi kodunuzdaki ilgili son satırı bulun.
- O satıra hangi değerlerin geldiğini kontrol edin.
- Sorunu daha küçük örnekle yeniden üretin.
Sistematik hata ayıklama
- Yeniden üret: Hangi girdiyle oluşuyor?
- Beklenen davranışı yaz: Ne olmalıydı?
- Küçült: Sorunlu işlemi mümkün olan en küçük örneğe indir.
- Gözlemle: Değerleri veya traceback’i incele.
- Hipotez kur: Tek bir olası neden seç.
- Tek değişiklik yap: Hipotezi sınayacak en küçük düzeltme.
- Yeniden doğrula: Hem sorunlu hem normal girdiyi dene.
Bu süreç hata ayıklamayı “rastgele satır değiştirme” işinden ölçülebilir bir incelemeye dönüştürür.
Modül nedir?
Standart kütüphaneden örnek:
import math
print(math.sqrt(81))
print(math.pi)Başka örnek:
from datetime import date
bugun = date.today()
print(bugun)Ana fikir: ihtiyacımız olan bazı işlevler Python’ın standart kütüphanesinde modüller olarak bulunur.
Tahmin et ve çalıştır — math
İkinci çıktı 8. haftadaki f-string biçimlendirmesiyle iki ondalık basamak gösterir.
Kendi modülümüz
hesaplar.py:
def kdv_hesapla(tutar, oran):
return tutar * oran
def indirimli_fiyat(fiyat, oran):
return fiyat - fiyat * oranBaşka dosyada:
import hesaplar
print(hesaplar.kdv_hesapla(500, 0.20))veya:
from hesaplar import kdv_hesapla
print(kdv_hesapla(500, 0.20))Main guard
def main():
print("Program başladı")
if __name__ == "__main__":
main()Temel fikir:
- Dosya doğrudan çalıştırılırsa
main()çağrılır, - Dosya modül olarak içe aktarılırsa bu çağrı otomatik çalışmaz.
Alıştırma — modül fonksiyonunu kullanın
Beklenen sonuç 5.0dır.
import math
def hipotenus(a, b):
return math.sqrt(a ** 2 + b ** 2)Mantık hatasında assert
def indirimli_fiyat(fiyat, oran):
return fiyat * oran
assert indirimli_fiyat(100, 0.20) == 80İstisna yoktur; assert beklenen davranış ile gerçek sonuç arasındaki farkı görünür kılar. Doğru fonksiyon:
def indirimli_fiyat(fiyat, oran):
return fiyat - fiyat * oranÜret — güvenli puan dönüştürme
Kurallar:
- Metin tam sayıya dönüştürülemiyorsa
None, - Sayı
0–100arasındaysa sayı, - Aralık dışındaysa
None, - Fonksiyon içinde
print()yok.
Beklenen: 85, None, None.
def puani_coz(metin):
try:
puan = int(metin)
except ValueError:
return None
if 0 <= puan <= 100:
return puan
return NoneFonksiyon çalıştıktan sonra yalnızca sonucu yazdırmak yerine sözleşmeyi de sınayın:
assert puani_coz("85") == 85
assert puani_coz("abc") is None
assert puani_coz("120") is NoneHata ayıklama kontrol listesi
- Hata türü nedir: söz dizimi, çalışma zamanı, mantık?
- Sorunu aynı girdiyle yeniden üretebiliyor muyum?
- Beklenen sonuç nedir?
- Sorunu daha küçük örneğe indirebilir miyim?
- Traceback hangi hata türünü, mesajı ve satırı gösteriyor?
- O satırdaki değişkenlerin değerleri nedir?
except ... as eile gerçek mesajı görmem yararlı olur mu?Nonedönen bir sözleşmeyi çağıran kod kontrol ediyor mu?- Tek bir hipotezi sınayacak küçük değişiklik yapabilir miyim?
- Düzeltme sonrası hem sorunlu hem normal örneği yeniden denedim mi?
Tek sayfa özet
- Söz dizimi hatası kodun yapısında, çalışma zamanı hatası yürütme sırasında, mantık hatası sonuç davranışında ortaya çıkar.
- Mantık hatasının düzeltilmiş sonucu da gösterilmelidir;
daire_cevresiiçin doğru formül2 * math.pi * yaricaptır. try/except, beklenen belirli istisnaları anlamlı biçimde ele almaya yarar; genişexceptgerçek hataları gizleyebilir.except ... as eistisnanın mesajını incelemeyi sağlar.- haftadaki dosya/sütun/dönüşüm sorunları
FileNotFoundError,KeyError,ValueErrorgibi belirli istisnalarla ele alınabilir.
- haftadaki dosya/sütun/dönüşüm sorunları
raise ValueError(...), fonksiyon sözleşmesi ihlalini çağıran koda açıkça bildirebilir.Nonehata/sonuç sinyali kullanılabilir; bunun bedeli çağıranın her seferindeis Nonekontrolü yapmasıdır.Noneiçin önerilen kontrolis None/is not Nonebiçimidir.- Traceback’te hata türü, mesaj ve kendi kodunuzdaki ilgili satır iyi başlangıç noktalarıdır.
- Hata ayıklama yeniden üretme, küçültme, gözlemleme, hipotez kurma ve doğrulama sürecidir.
- Standart kütüphane modülleri
importile kullanılabilir; kendi Python dosyalarımız da modül olabilir. - Main guard, dosyanın doğrudan çalıştırılması ile modül olarak içe aktarılması davranışlarını ayırır.
Bir sonraki bölümde yeni kavram eklemeyeceğiz. İlk 13 haftadaki araçları birlikte kullanarak CSV, modül, hata yönetimi, koleksiyonlar ve doğrulamayı birleştiren küçük bir bütünleştirici uygulama geliştireceğiz.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
- Söz dizimi, çalışma zamanı ve mantık hatasını birbirinden ayırır.
try/exceptile belirli bir istisnayı ele alır ve genişexceptriskini bilir.FileNotFoundError,KeyErrorveValueError’ı doğru duruma eşler.raiseile sözleşme ihlalini bildirir ve bunuassertten ayırır.Nonesinyalini kullanır veis Nonedenetimini uygular.- Traceback’ten hata türünü, mesajı ve kendi kodundaki ilgili satırı okur.
importile modül kullanır ve main guard’ın ne ayırdığını açıklar.