Nesne Tabanlı Programlama

11 - Hata Yakalama - Hatalarla Baş Etmenin Yolları

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2024

Hatalar: Yazılımın Kaçınılmaz Gerçeği

  • Programlamada hatalar (errors), kaçınılmaz bir gerçektir.
  • Hatalar farklı şekillerde ortaya çıkabilir
    • yazım hataları,
    • mantık hataları,
    • sistem hataları

Hata yönetimi

  • Programlarımızın beklenmedik durumlarla başa çıkabilmesi ve hataları zarif bir şekilde ele alabilmesi için önemlidir.
  • Python, hata yakalama (exception handling) mekanizması ile hataları yönetmemizi sağlar.

Hata Türleri (Exceptions)

  • Python’da, hatalar exception (istisna) olarak adlandırılır.
  • Python, birçok farklı exception türü tanımlar.

SyntaxError

  • Python sözdiziminde bir hata olduğunda oluşur.
  • Yorumlayıcı kodu ayrıştırırken hatayı fark eder ve çalıştırmadan önce bildirir.
print("Merhaba Dünya!"
# SyntaxError: invalid syntax

NameError

  • Tanımlanmamış bir değişken kullanıldığında oluşur.
print(degisken)
# NameError: name 'degisken' is not defined

TypeError

  • Bir fonksiyona yanlış veri tipi verildiğinde oluşur.
  • Fonksiyon, beklenen veri tipiyle uyumlu olmayan bir argüman aldığında bu hata oluşur.
len(10)
# TypeError: object of type 'int' has no len()

ValueError

  • Bir fonksiyona doğru veri tipinde ancak geçersiz bir değer verildiğinde oluşur.
int("abc")
# ValueError: invalid literal for int() with base 10: 'abc'

IndexError

  • Bir listenin, demetin veya stringin geçersiz bir indeksi kullanıldığında oluşur.
liste = [1, 2, 3]
print(liste[3])
# IndexError: list index out of range

KeyError

  • Bir sözlükte bulunmayan bir anahtar kullanıldığında oluşur.
sozluk = {"isim": "Ahmet"}
print(sozluk["yas"])  # KeyError: 'yas'

ZeroDivisionError

  • Bir sayı sıfıra bölündüğünde oluşur.
10 / 0  # ZeroDivisionError: division by zero

FileNotFoundError

  • Açmaya çalıştığınız dosya bulunamadığında oluşur.
open("file.txt", "r")
# FileNotFoundError: [Errno 2] No such file or directory: 'file.txt'

try-except Blokları

  • Python’da, hataları yakalamak ve yönetmek için try-except blokları kullanılır.
    • try bloğu içinde, hata oluşabilecek kodlar yer alır.
    • except bloğu, hata oluştuğunda yapılacak işlemleri içerir.
    • try bloğu içinde bir hata oluşursa, programın akışı except bloğuna yönlendirilir.

Sözdizim

try:
    # Hata oluşabilecek kod bloğu
except HataTürü:
    # Hata oluştuğunda çalıştırılacak kod bloğu

Örnek:

try:
    sayi = int(input("Bir sayı girin: "))
    sonuc = 10 / sayi
    print(sonuc)
except ZeroDivisionError:
    print("Sıfıra bölme hatası!")

Birden fazla except bloğu

  • Bir try bloğunda birden fazla hata türü oluşabilir.
  • Her hata türünü ayrı ayrı yakalamak için birden fazla except bloğu kullanabiliriz.

Örnek:

try:
    sayi1 = int(input("Birinci sayıyı girin: "))
    sayi2 = int(input("İkinci sayıyı girin: "))
    sonuc = sayi1 / sayi2
    print(sonuc)
except ZeroDivisionError:
    print("Sıfıra bölme hatası!")
except ValueError:
    print("Geçersiz sayı girişi!")

Yakalanan hataya erişim

  • except bloğunda, as anahtar kelimesi kullanarak yakalanan hataya bir isim verilebilir ve hata mesajına bu isimle erişilebilir.
  • Bu özellik, hatalar hakkında daha fazla bilgi edinmemizi ve programımızın davranışını buna göre ayarlamamızı sağlar.

Örnek:

try:
    sonuc = 10 / 0
except ZeroDivisionError as hata:
    print("Hata mesajı:", hata)  # Çıktı: Hata mesajı: division by zero

else Bloğu: Hata olmadığında çalıştırılacak kodlar

  • else bloğu, try bloğu içinde hiçbir hata oluşmadığında çalıştırılacak kodları içerir.
  • Bu blok, try bloğunda hata oluşması durumunda çalıştırılmak istenmeyen kodları ayırmak için kullanılır.

Örnek

try:
    sayi = int(input("Bir sayı girin: "))
    bolum = 10 / sayi
except (ValueError, ZeroDivisionError):
    print("Hata oluştu!")
else:
    print("10'un sayıya bölümü:", bolum)

Neden else Bloğu Kullanmalıyız?

  • Kod Okunabilirliği: else bloğu, hata durumunda çalıştırılmaması gereken kodları try bloğundan ayırır ve kodun daha okunabilir olmasını sağlar.
  • Mantıksal Akış: try bloğu içinde hata oluşursa, except bloğu çalışır ve else bloğu atlanır. Bu, programın mantıksal akışını daha net bir şekilde ifade eder.

finally Bloğu: Her zaman çalıştırılacak kodlar

  • finally bloğu, try bloğunda hata olsa da olmasa da her zaman çalıştırılır.
  • Bu blok genellikle, dosya kapatma, bağlantı sonlandırma gibi temizlik işlemleri için kullanılır.

Neden finally bloğu kullanmalıyız?

  • Temizlik İşlemleri: finally bloğu, try bloğunda hata olsa bile, kaynakların serbest bırakılması (dosya kapatma, bağlantı sonlandırma gibi) gibi önemli temizlik işlemlerinin yapılmasını garanti eder.
  • Kaynak Yönetimi: finally bloğu, programın normal akışında veya bir hata durumunda, kaynakların düzgün bir şekilde yönetilmesini sağlar.

Örnek:

try:
    dosya = open("dosya.txt", "r")
    icerik = dosya.read()
    print(icerik)
except FileNotFoundError:
    print("Dosya bulunamadı!")
finally:
    dosya.close()  # Dosyayı her zaman kapatırız

Hata Mesajlarını Özelleştirme

  • raise anahtar kelimesi ile kendi hata mesajlarımızı oluşturabiliriz.

Örnek:

def yas_kontrol(yas):
    if yas < 0:
        raise ValueError("Yaş negatif olamaz!")

try:
    yas_kontrol(-5)
except ValueError as hata:
    print(hata)  # Çıktı: Yaş negatif olamaz!

graph LR
A[try] --> B{Hata var mı??};
B -- Evet --> C(except);
B -- Hayır --> D(else);
C --> E(finally);
D --> E;
E --> F(Diğer);

Hata Yakalama Stratejileri

  • Genel Exception bloğu yerine belirli hata türleri için ayrı except blokları kullanın: Bu, farklı hata türlerine farklı şekilde yanıt vermenizi sağlar.

  • Hataları yakalamak yerine önlemeye çalışın: Hataları önlemek, programınızın daha güvenilir olmasını sağlar. Örneğin, bir sayıyı sıfıra bölmeden önce, bölenin sıfır olmadığını kontrol edin.

Hata Yakalama Stratejileri - 2

  • Hata Günlüğü (Logging): Hataları log dosyalarına kaydederek, hata ayıklama ve sorun giderme işlemlerini kolaylaştırın. Python’un logging modülünü kullanarak, hataları farklı seviyelerde (debug, info, warning, error, critical) loglayabilirsiniz. Log dosyaları, programın davranışı hakkında değerli bilgiler sağlar ve sorunların teşhis edilmesine yardımcı olur.

  • Hataları Kullanıcı Dostu Hale Getirme: Teknik hata mesajlarını, kullanıcıların anlayabileceği şekilde daha kullanıcı dostu mesajlara dönüştürün. Örneğin, FileNotFoundError hatası alındığında, kullanıcıya “Belirtilen dosya bulunamadı. Lütfen dosya yolunu kontrol edin.” gibi bir mesaj gösterin.

Hata Günlüğü (Logging)

Hataları Kaydedin ve Analiz Edin

  • Hata yönetimi, programınızın hatalarla karşılaştığında çökmesini engellemekle kalmaz, aynı zamanda bu hatalardan ders çıkarma fırsatı da sunar.
  • Hata günlüğü (logging), programınızın çalışması sırasında oluşan hataları, uyarıları ve diğer önemli olayları bir dosyaya veya başka bir hedefe kaydetmenizi sağlar.

logging modulü

  • Python’un yerleşik logging modülü, esnek ve güçlü bir hata günlüğü sistemi sunar.
  • Bu modül ile hataları farklı seviyelerde kaydedebilir, log mesajlarını özelleştirebilir ve logları farklı hedeflere yönlendirebilirsiniz.

Loglama seviyeleri

  • logging modülü, hataları ve diğer olayları önem derecesine göre sınıflandırmak için beş farklı seviye sunar:

    • DEBUG: Ayrıntılı bilgi, genellikle hata ayıklama için kullanılır.
    • INFO: Programın normal çalışması hakkında bilgi verir.
    • WARNING: Potansiyel bir sorun olduğunu gösterir, ancak program çalışmaya devam eder.
    • ERROR: Programın belirli bir işlemi gerçekleştirmesini engelleyen bir hata oluştuğunu gösterir.
    • CRITICAL: Programın çalışmasını durduran kritik bir hata olduğunu gösterir.

logging modülünü kullanma

  1. Logger Oluşturma: logging.getLogger(__name__) ile bir logger nesnesi oluşturun.
  2. Handler Ekleme: Log mesajlarının nereye yazılacağını belirlemek için bir handler ekleyin. Örneğin, logging.FileHandler('hata.log') ile log mesajlarını bir dosyaya yazabilirsiniz.

logging modülünü kullanma

  1. Formatter Ayarlama: Log mesajlarının formatını belirlemek için bir formatter ayarlayın. Örneğin, logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') ile log mesajlarına zaman damgası, log seviyesi ve mesaj ekleyebilirsiniz.
  2. Log Mesajlarını Yazma: logger.debug(), logger.info(), logger.warning(), logger.error() ve logger.critical() metotlarını kullanarak log mesajları yazın.

Örnek

import logging

# Logger oluştur
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # Log seviyesini DEBUG olarak ayarla

# FileHandler ekle ve hata.log dosyasına yaz
handler = logging.FileHandler('hata.log')
handler.setLevel(logging.ERROR)  # Handler seviyesini ERROR olarak ayarla
# Formatter ayarla
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

# Logger'a handler ekle
logger.addHandler(handler)

# Örnek kod
try:
    sonuc = 10 / 0
except ZeroDivisionError as e:
    # Hata mesajını log dosyasına yaz
    logger.error('Sıfıra bölme hatası: %s', e)
2024-11-16 17:30:00,123 - ERROR - Sıfıra bölme hatası: division by zero

Loglamada İyi Uygulamalar

  • Doğru Log Seviyesini Kullanın: Mesajın önemini belirtin
# Kullanıcının oturum açma denemesi başarılı
logger.info('Kullanıcı %s başarıyla oturum açtı.', kullanici_adi)

# Veri tabanı bağlantısı kurulamadı
logger.error('Veri tabanı bağlantı hatası: %s', hata_mesaji)

# Disk alanı azalıyor
logger.warning('Disk alanı kritik seviyede (%s GB)!', kalan_alan)
  • Açık ve Bilgilendirici Mesajlar Yazın: Hızlı anlama ve sorun giderme

İyi bir log örneği

    2024-11-16 18:00:00,123 - ERROR - Veri tabanı bağlantı hatası: Kullanıcı adı veya şifre yanlış (kullanici_adi: test_kullanici, veri_tabani: my_database)

Kötü bir log örneği

Hata!

İpuçları

  • Mesajlar, tam cümleler yerine, anahtar kelimeler ve değişken değerleri içeren öz ifadeler şeklinde olabilir.
  • Hatanın nedenini veya bağlamını açıklayan ek bilgiler ekleyin.
  • Değişken değerlerini, hata kodlarını ve diğer ilgili verileri log mesajına dahil edin.
  • Log mesajlarını tutarlı bir şekilde formatlayın.
  • Logları Uygun Hedeflere Yönlendirin: Okunabilirlik ve erişilebilirlik

  • logging modülü, log mesajlarını farklı hedeflere yönlendirmek için handler’lar sunar. En yaygın kullanılan handler’lar:

    • FileHandler: Log mesajlarını bir dosyaya yazar.
    • StreamHandler: Log mesajlarını konsola (standart çıktı) veya başka bir akışa yazar.
    • SMTPHandler: Log mesajlarını e-posta ile gönderir.
  • Log Rotasyonu: Disk alanını verimli kullanın.

  • Log dosyaları zamanla büyüyebilir ve disk alanını tüketebilir.

  • Log rotasyonu, log dosyalarının boyutunu ve sayısını kontrol altında tutmak için kullanılan bir tekniktir.

  • logging modülü, log rotasyonunu destekleyen RotatingFileHandler ve TimedRotatingFileHandler gibi handler’lar sunar.

  • Bağlamsal Bilgi Ekleyin: Hataları Anlamayı Kolaylaştırın

  • Log mesajlarına, hatanın veya olayın bağlamını anlamak için gerekli bilgileri ekleyin. *Örneğin, kullanıcı kimliği, istek URL’si, işlem kimliği gibi bilgiler, hatayı daha hızlı bir şekilde teşhis etmenize yardımcı olabilir.

Alıştırma Soruları

Problemleri try-except bloklarını kullanarak çözün:

1. Dosya Okuma ve Sayısal Veri İşleme

Problem: Kullanıcıdan bir dosya adı alınız. Bu dosyayı okuyunuz ve içindeki her satırın bir sayı olduğunu varsayarak, bu sayıları toplayınız. Dosya bulunamazsa, kullanıcıya “Dosya bulunamadı!” uyarısı veriniz. Dosya içindeki satırlar sayıya dönüştürülemezse (örneğin, harf içeren bir satır varsa), bu satırı atlayıp bir sonraki satıra geçiniz ve kullanıcıya “Geçersiz veri içeren satır(lar) atlandı.” uyarısı veriniz. Son olarak, toplamı ekrana yazdırınız.

İpucu: open(), readlines(), int(), try-except, FileNotFoundError, ValueError

def dosya_toplami():
    try:
        dosya_adi = input("Dosya adını girin: ")
        with open(dosya_adi, "r") as dosya:
            satirlar = dosya.readlines()
        toplam = 0
        gecersiz_satir_sayisi = 0
        for satir in satirlar:
            try:
                sayi = int(satir)
                toplam += sayi
            except ValueError:
                gecersiz_satir_sayisi += 1
        if gecersiz_satir_sayisi > 0:
            print("Geçersiz veri içeren satır(lar) atlandı.")
        print("Toplam:", toplam)
    except FileNotFoundError:
        print("Dosya bulunamadı!")
dosya_toplami()

2. Kullanıcı Girişi ve Bölme İşlemi

Problem: Kullanıcıdan iki sayı alınız (x ve y). x’i y’ye bölünüz ve sonucu ekrana yazdırınız. Kullanıcı sayı yerine harf veya başka bir karakter girerse, “Geçersiz giriş! Lütfen sayı giriniz.” uyarısı veriniz. Y sayısı sıfır ise, “Sıfıra bölme hatası!” uyarısı veriniz.

İpucu: input(), int(), try-except, ValueError, ZeroDivisionError

def bolme_islemi():
    try:
        x = int(input("Birinci sayıyıgirin (x): "))
        y = int(input("İkinci sayıyıgirin (y): "))
        sonuc = x / y
        print("Sonuç:", sonuc)
    except ValueError:
        print("Geçersiz giriş! Lütfensayı giriniz.")
    except ZeroDivisionError:
        print("Sıfıra bölme hatası!")
bolme_islemi()

3. Sözlük Veri Yapısı ve Hata Yönetimi

Problem: Bir öğrencinin notlarını saklamak için bir sözlük kullanıyorsunuz. Sözlükte öğrencinin adı anahtar, notları ise bir liste olarak değer olarak tutuluyor. Kullanıcıdan öğrencinin adını ve yeni notunu alınız. Eğer öğrenci sözlükte yoksa, “Öğrenci bulunamadı!” uyarısı veriniz ve öğrenciyi sözlüğe ekleyip notunu giriniz. Eğer öğrencinin notları arasında 100’den büyük veya 0’dan küçük bir not varsa, “Geçersiz not! Not 0-100 aralığında olmalıdır.” uyarısı veriniz ve notu eklemeyiniz. Aksi takdirde, notu öğrencinin not listesine ekleyiniz. Son olarak, öğrencinin güncel notlarını ekrana yazdırınız.

İpucu: Sözlükler, try-except, KeyError, ValueError

def not_ekle():
    ogrenci_notlari = {
        "Ahmet": [80, 90, 75],
        "Ayşe": [95, 100, 85],
    }
    try:
        ad = input("Öğrencinin adınıgirin: ")
        not_ = int(input("Notu girin:"))
        if not_ < 0 or not_ > 100:
            raise ValueError("Geçersiznot! Not 0-100 aralığındaolmalıdır.")
        if ad in ogrenci_notlari:
            ogrenci_notlari[ad].appen(not_)
        else:
            print("Öğrencibulunamadı!")
            ogrenci_notlari[ad] =[not_]
        print(f"{ad} öğrencisininnotları: {ogrenci_notlari[ad]")
    except ValueError as e:
        print(f"Hata: {e}")
    except KeyError:
        print("Öğrenci adı geçerli birstring olmalıdır.")
not_ekle()

4. Liste İndeksi ve Hata Yakalama

Problem: Bir listede belirli bir indeksteki elemanı kullanıcıya göstermek istiyorsunuz. Kullanıcıdan bir indeks numarası alınız. Eğer girilen indeks numarası liste sınırları içindeyse, o indeksteki elemanı ekrana yazdırınız. Eğer indeks numarası liste sınırları dışında ise veya sayı dışında bir değer girildiyse, “Geçersiz indeks! Lütfen geçerli bir indeks numarası giriniz.” uyarısı veriniz.

İpucu: Listeler, try-except, IndexError, ValueError

def indeks_elemani():
    my_list = ["elma", "armut","çilek", "muz"]
    try:
        indeks = int(input("İndeksnumarasını girin: "))
        if 0 <= indeks < len(my_list):
            print(f"Listedeki{indeks}. eleman: {my_lis[indeks]}")
        else:
            raise IndexError("Geçersizindeks!")
    except IndexError:
        print("Geçersiz indeks! Lütfengeçerli bir indeks numarasıgiriniz.")
    except ValueError:
        print("Geçersiz giriş! Lütfenbir sayı giriniz.")
indeks_elemani()