İ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 hafta neleri yapabilmelisiniz?

Bölümün sonunda:

  • Söz dizimi, çalışma zamanı ve mantık hatalarını ayırt edebilmeli,
  • İstisnanın ne olduğunu açıklayabilmeli,
  • try/except ile ValueError, FileNotFoundError, KeyError gibi belirli hata türlerini yakalayabilmeli,
  • except ... as e ile istisna mesajını inceleyebilmeli,
  • Gereğinden geniş except kullanımının neden sorunlu olduğunu açıklayabilmeli,
  • Bir fonksiyon sözleşmesi ihlalinde raise ValueError(...) kullanma fikrini açıklayabilmeli,
  • None ile hata/sonuç sinyali vermenin çağıran kod açısından bedelini açıklayabilmeli,
  • None kontrolünde is None / is not None kullanımını tanıyabilmeli,
  • Bir traceback’in hata türünü, mesajını ve ilgili satırı bulmak için nasıl okunacağını açıklayabilmeli,
  • Standart kütüphaneden bir modülü import ile kullanabilmeli,
  • Kendi modülünüzü oluşturma fikrini ve main guard yapısının temel amacını açıklayabilmeli,
  • Bir hatayı yeniden üretme, küçültme, tahmin kurma ve doğrulama adımlarıyla sistematik biçimde inceleyebilmelisiniz.

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]            # IndexError

Kod 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ı eksik

Doğru formülü standart kütüphanedeki math.pi ile açıkça yazabiliriz:

import math

def daire_cevresi(yaricap):
    return 2 * math.pi * yaricap
assert abs(daire_cevresi(1) - 2 * math.pi) < 0.000001

Bu tür sorunları Python otomatik olarak her zaman fark edemez; beklenen davranışla karşılaştırmak gerekir.

Important

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.

Söz dizimi, çalışma zamanı ve mantık hatalarının karşılaştırılması.

Programlama hatalarının türleri

İ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.

Note

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.

Warning

“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)
Important

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

  1. haftada assert ifadelerini 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.

Note

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 None

Avantajı: ç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 toplanamaz

Hata 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.

Important
  1. haftadaki bütünleştirici uygulamada if kayit is None: ve assert ... is None göreceksiniz. Bunlar == operatörünün rastgele bir varyasyonu değil, None için bilinçli kimlik kontrolleridir.

Noneraise 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:

  1. En alttaki hata türünü bulun.
  2. Hata mesajını okuyun.
  3. Kendi kodunuzdaki ilgili son satırı bulun.
  4. O satıra hangi değerlerin geldiğini kontrol edin.
  5. Sorunu daha küçük örnekle yeniden üretin.

Sistematik hata ayıklama

  1. Yeniden üret: Hangi girdiyle oluşuyor?
  2. Beklenen davranışı yaz: Ne olmalıydı?
  3. Küçült: Sorunlu işlemi mümkün olan en küçük örneğe indir.
  4. Gözlemle: Değerleri veya traceback’i incele.
  5. Hipotez kur: Tek bir olası neden seç.
  6. Tek değişiklik yap: Hipotezi sınayacak en küçük düzeltme.
  7. 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 * oran

Baş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

Bir Python dosyasının doğrudan çalıştırılması ile modül olarak import edilmesinde main guard davranışı.

main guard ve import davranışı
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ı 0100 arası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 None

Fonksiyon ç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 None

Hata 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 e ile gerçek mesajı görmem yararlı olur mu?
  • None dö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?

Bölüm özeti

  • 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_cevresi için doğru formül 2 * math.pi * yaricaptır.
  • try/except, beklenen belirli istisnaları anlamlı biçimde ele almaya yarar; geniş except gerçek hataları gizleyebilir.
  • except ... as e istisnanın mesajını incelemeyi sağlar.
    1. haftadaki dosya/sütun/dönüşüm sorunları FileNotFoundError, KeyError, ValueError gibi belirli istisnalarla ele alınabilir.
  • raise ValueError(...), fonksiyon sözleşmesi ihlalini çağıran koda açıkça bildirebilir.
  • None hata/sonuç sinyali kullanılabilir; bunun bedeli çağıranın her seferinde is None kontrolü yapmasıdır.
  • None için önerilen kontrol is None / is not None biç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 import ile 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.

Kendinizi kontrol edin

  1. SyntaxError, ValueError ve mantık hatası arasındaki fark nedir?
  2. daire_cevresi için doğru formül nedir?
  3. except ValueError as e: içindeki e neyi temsil eder?
  4. FileNotFoundError ve KeyError hangi iki dosya/CSV problemine karşılık gelebilir?
  5. raise ValueError(...) ile assert hangi amaçlarda ayrılır?
  6. None döndürmenin çağıran kod açısından temel bedeli nedir?
  7. Neden sonuc is None biçimini kullanırız?
  8. Traceback okurken hangi üç bilgiyi aramak yararlıdır?
  9. import math ne yapar?
  10. Main guard hangi iki kullanım biçimini ayırmaya yardım eder?

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.

Back to top