Dosyalar, istisnalar ve sistematik hata ayıklama
Gerçek bir programa her zaman doğru girdi gelmez. Dosya bulunmayabilir, kullanıcı sayı yerine metin girebilir, bir kayıt eksik olabilir ya da program çalışıp yanlış sonuç verebilir. Program çalışırken böyle bir sorun çıkınca Python normal akışı keser ve bir istisna (exception) oluşturur. Bu hafta temel dosya işlemlerini ve istisnaları kullanacağız. Bir hata mesajı gördüğümüzde kodu rastgele değiştirmeyecek, mesajı sorunun nedenini gösteren bir kanıt gibi okuyacağız.
Bu bölümün kapsamı
with open(...)kalıbı ve dosyanın blok bitiminde kapanması"r","w"ve"a"kipleri;"w"kipinin yıkıcılığı- Metin dosyalarında neden
encoding="utf-8"yazıldığı - Söz dizimi, çalışma zamanı ve mantık hatalarının ayrımı
raiseileprintfarkı- Belirli istisna türünü yakalama ve istisnayı sessizce yutmanın zararı
- Traceback çıktısını hata türünden başlayarak okuma
pathlibve ikili dosya kipleriloggingmodülü- Kendi istisna sınıfını tanımlama (12. bölümün konusu)
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Hata oluştuğunda programın ne yapmasını istiyoruz?
Bir hatayı ekrana yazmak, yakalayıp yutmak ve fonksiyonu çağıran koda bildirmek üç ayrı karardır. Üçü de birkaç satır tutar. Aralarındaki fark, sorun çıktığında kimin haberi olduğudur: ekrana yazarsanız kullanıcı görür, yutarsanız kimse görmez, bildirirseniz fonksiyonu çağıran kod öğrenir ve ne yapacağına kendisi karar verir.
Dosyayı güvenli biçimde açmak
with open("notes.txt", "r", encoding="utf-8") as file:
content = file.read()
print(content)with bloğu tamamlandığında dosya kapatılır. Elle close() çağrısını unutma riski azalır.
Dosya modları: r, w, a
| Mod | Anlam | Dikkat edilecek nokta |
|---|---|---|
"r" |
oku | dosya yoksa FileNotFoundError |
"w" |
yaz | dosya varsa mevcut içeriği silip baştan yazar |
"a" |
sona ekle | mevcut içeriği korur, sonuna yazar |
"w" modu yıkıcıdır
with open("odev.txt", "w", encoding="utf-8") as file:
file.write("Yeni içerik")odev.txt zaten varsa eski içerik silinir. Var olan dosyanın sonuna eklemek istiyorsanız "a" kullanın. Modu yazmadan önce kendinize “eski içerik kalmalı mı?” diye sorun.
Bu sayfadaki kod hücreleri tarayıcının içinde sanal bir dosya sistemi kullanır. Bu yüzden küçük dosya deneylerini burada yapabiliriz:
Neden encoding="utf-8" yazıyoruz?
Metin dosyası baytlardan oluşur; karakterlerin bu baytlara nasıl çevrileceğini bir karakter kodlaması belirler. İşletim sisteminin varsayılan kodlamasına güvenirseniz aynı dosya farklı makinelerde farklı okunabilir; bu sorun özellikle Windows’ta ve Türkçe ç, ğ, ı, İ, ö, ş, ü karakterlerinde çıkar.
Bu ders boyunca metin dosyalarında açıkça:
encoding="utf-8"kullanacağız. Böylece dosyanın hangi kodlamayla okunup yazılacağı makineden makineye değişmez.
Satır satır okuma
Büyük ya da satır satır işlenecek dosyalarda bütün içeriği read() ile belleğe almak zorunda değiliz. Dosya nesnesini bir for döngüsüyle satır satır dolaşabiliriz:
Bu derste dosyaları çoğunlukla for line in file ile okuyacağız. readlines() bütün satırları bir listeye yükler. Küçük dosyalarda sorun çıkarmaz ama satır satır yapılan işlerin çoğunda gerekmez.
Üç hata sınıfını ayırın
Söz dizimi hatası
if score > 50
print("Geçti")if satırının sonundaki : eksik. Python kodu okurken takılır ve dosyanın tek satırını bile çalıştırmaz. C’de derleyicinin verdiği söz dizimi hatalarına benzer.
Çalışma zamanı hatası
score = int("seksen")Satırın yazımı doğrudur ama satır çalışırken sorun çıkar: "seksen" metni tam sayıya çevrilemez.
Mantık hatası
# Yüzde 20 indirim yapmak isterken:
final = price * 1.20Program çalışır ama yanlış sonuç verir. Mantık hataları çoğu zaman istisna oluşturmaz. Onları bulmak için test yazmak ve programı örnek girdilerle denemek gerekir.
Hatayı kendiniz yükseltmek: raise
Bazı hataları Python kendisi oluşturur: int("abc") çalışınca ValueError çıkar. Bazı durumların geçersiz olduğunu ise Python bilemez, bunu programın kuralları söyler. Örneğin hesaptan bakiyeden fazla para çekilemez. Programın konusundan gelen böyle kurallara iş kuralı denir. Bir iş kuralı bozulduğunda istisnayı raise ile kendimiz yükseltiriz:
raise ValueError(...) çalıştığı anda normal akış kesilir ve uygun except bloğu aranır. raise ile print("hata") aynı şey değildir: print yalnızca mesaj yazar, raise ise fonksiyonu çağıran koda işlemin normal biçimde tamamlanamadığını bildirir.
6–12. haftalarda nesnelerin uyması gereken kuralları (örneğin “bakiye eksiye düşemez”) korurken en çok raise kullanacağız. Nesnenin her zaman uyması gereken bu tür kurallara 8. haftada değişmez koşul (invariant) adını vereceğiz.
try / except: hatayı saklamak için değil yönetmek için
as error yakalanan istisnayı error adına bağlar. Hata mesajını yazdırmak ya da ayrıntısına bakmak için bu adı kullanırız.
except Exception: yazıp her hatayı yakalamak başta rahat görünebilir. Ama bu, yanlış yazılmış bir ad ya da uyuşmayan türler gibi kodunuzdaki hataları da gizleyebilir. Hangi hatayı bekliyorsanız except satırına onun türünü yazın.
try:
risky_operation()
except Exception:
passBu kalıp neyin yanlış gittiğini gizler. Öğrenirken hata mesajını görmek, onu susturmaktan çoğu zaman daha yararlıdır.
else ve finally
try deyiminin tam biçiminde şu bloklar da bulunabilir:
try:
value = int(raw)
except ValueError as error:
print("Dönüşüm başarısız:", error)
else:
print("Dönüşüm başarılı:", value)
finally:
print("Bu satır her durumda çalışır")else,trybloğu hiç istisna oluşmadan biterse çalışır.finally, istisna oluşsa da oluşmasa da çalışır. Açılan bir dosyayı kapatmak gibi her durumda yapılması gereken işler için kullanılır.
Dosyaları çoğu zaman with ile açtığımız için finally içine elle close() yazmamız gerekmez. Yine de finally başka kodlarda karşınıza çıkacak; ne işe yaradığını bilin.
Hata iz dökümü (traceback) nasıl okunur?
Yakalanmayan bir istisna programı durdurduğunda Python ekrana şöyle bir döküm yazar:
Traceback (most recent call last):
File "app.py", line 12, in <module>
total = calculate(data)
File "app.py", line 5, in calculate
return 100 / data["count"]
ZeroDivisionError: division by zero
İlk bakılacak yer en alttaki satırdır: hata türü ZeroDivisionError. Sonra hemen üstte hatanın oluştuğu kod satırını bulun. Daha yukarıdaki satırlar o noktaya hangi fonksiyon çağrılarıyla gelindiğini gösterir.
Hata ayıklama döngüsü
Hata ayıklama (debugging), bir hatanın nedenini bulup düzeltme işidir. Rastgele denemek yerine şu adımları izleyin:
- Hatayı tekrarlayın: Hata hangi girdide her seferinde oluşuyor?
- Daraltın: Hatayı hâlâ veren en küçük kod parçası hangisi?
- Kanıt toplayın: Traceback’i okuyun, ara değerleri
printvetypeile yazdırın. - Hipotez kurun: Sorunun nedeni ne olabilir?
- Tek değişiklik yapın: Aynı anda birkaç şeyi rastgele değiştirmeyin.
- Yeniden deneyin: Hata gitti mi? Başka bir şey bozuldu mu?
Nesne Tabanlı Programlama II dersinde aynı adımları debugger (kodu satır satır çalıştırıp değişkenlere bakmanızı sağlayan araç), logging ve testlerle izleyeceğiz.
Alıştırma: Doğru hata türünü yakala
except ValueError as error:Alıştırma: raise ile hata yükselt
Aşağıdaki fonksiyonu tamamlayın. score 0–100 aralığının dışındaysa ValueError yükseltmeli, geçerliyse değeri döndürmelidir:
Fonksiyonu sınırdaki -1, 0, 100 ve 101 değerleriyle ayrı ayrı deneyin.
Alıştırma: Mantık hatasını bul
Program hata vermeden çalışıyor. Ama yüzde 20 indirimde beklenen sonuç 80. Bu hatanın neden istisna oluşturmadığını açıklayın ve fonksiyonu düzeltin.
Alıştırma: Dosya verisini doğrula
Kodu çalıştırmadan önce okuyun ve hangi satırın atlanacağını tahmin edin. Sonra bozuk yerine 30 yazıp yeniden çalıştırın.
Sıra sizde: Dayanıklı kayıt okuyucu
name,score biçiminde dört satırlık bir dosya oluşturun. Bir satırda geçersiz puan bulunsun. Program:
- Dosyayı
encoding="utf-8"ile açsın, for line in fileile dolaşsın,- Geçerli kayıtları listeye eklesin,
- Geçersiz satırı
except ValueError as errorile açık mesajla bildirsin, - Geçerli puanların ortalamasını yazsın,
- Hiç geçerli kayıt yoksa sıfıra bölme hatasına düşmesin,
- Sonuç raporunu ayrı bir dosyaya
"w"moduyla, eski içeriğin silineceğini bilerek yazsın.
Çalışır kod görevi: Programı üç dosyayla çalıştırın: düzgün bir dosya, bir satırı bozuk bir dosya ve hiç geçerli kaydı olmayan bir dosya. Üçünde de program çökmeden bitmelidir.
Python köprüsünün kapanışı
İlk beş haftada Python’ın temel araçlarını hızlıca gözden geçirdik. Bundan sonra sorduğumuz soru değişiyor. “Python’da döngü nasıl yazılır?” yerine şuna benzer sorular soracağız: “Bir banka hesabının bakiyesi nerede durmalı, onu hangi kod değiştirebilmeli?” C’de bir struct ile onu değiştiren fonksiyonlar ayrı ayrı dururdu. Nesne yönelimli programlamada (OOP) veri ile onu değiştiren kod aynı yapıda toplanır.
Elimizde şu beceriler var:
- Değer ve türlerle çalışma,
- Kontrol akışı,
- Koleksiyonlar ve iki adın aynı nesneyi gösterebilmesi,
- İşi fonksiyonlara bölme,
- Modüller, dosyalar ve istisnalar,
- Geçersiz durumu
raiseile fonksiyonu çağıran koda bildirme.
OOP’yi bu becerilerin üzerine kuracağız.
Tek sayfa özet
- Hata mesajını kanıt gibi okuyun; mesajı görünce kodu rastgele değiştirmeye başlamayın.
raisenormal akışı keser ve geçersiz durumu fonksiyonu çağıran koda bildirir.- Yalnızca beklediğiniz istisna türünü yakalayın; gerekirse ayrıntısına
as errorile bakın. "w"var olan dosyanın içeriğini siler;"a"sonuna ekler.- Türkçe karakterli metinler her makinede aynı okunsun diye kodlamayı açıkça
utf-8olarak belirtin. - Dosya satırlarını çoğu zaman doğrudan
for line in fileile dolaşabilirsiniz. - Kodun çalışması, doğru olduğunu göstermez.
- Gelecek haftadan itibaren “stok eksiye düşemez” gibi kuralları nesnelerin içinde
raiseile koruyacağız.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
with open(...)kalıbını kullanır ve dosyanın blok bitiminde kapandığını bilir."r","w"ve"a"kiplerini ayırır ve"w"kipinin eski içeriği sildiğini bilir.- Metin dosyalarında
encoding="utf-8"yazmanın gerekçesini açıklar. - Söz dizimi, çalışma zamanı ve mantık hatalarını birbirinden ayırır.
raiseileprintfarkını gösterir ve iş kuralı bozulduğunda istisna yükseltir.- Belirli bir istisna türünü yakalar ve istisnayı yakalayıp hiçbir şey yapmamanın neden zararlı olduğunu açıklar.
- Traceback çıktısını hata türünden başlayarak okur.