Hata Yakalama¶
Şimdiye kadar yazdığımız bütün programlar, dikkat ettiyseniz tek bir ortak varsayım üzerine kurulu. Buna göre biz, yazdığımız programın kullanıcı tarafından nasıl kullanılmasını istiyorsak, her zaman o şekilde kullanılacağını varsayıyoruz. Örneğin sayıları toplayan bir program yazdığımızda, kullanıcının her zaman sayı değerli bir veri gireceğini düşünüyoruz. Ancak bütün iyi niyetimize rağmen, yazdığımız programlarda işler her zaman beklediğimiz gibi gitmeyebilir. Örneğin, dediğimiz gibi, yazdığımız programı, kullanıcının bir sayı girmesi temeli üzerine kurgulamışsak, kullanıcının her zaman sayı değerli bir veri gireceğinden emin olamayız.
Mesela şöyle bir program yazdığımızı düşünün:
veri1 = input("Karekökünü hesaplamak istediğiniz sayı: ")
karekök = int(veri1) ** 0.5
print(veri1, "sayısının karekökü: ", karekök)
veri2 = input("Karesini hesaplamak istediğiniz sayı: ")
kare = int(veri2) ** 2
print(veri2, "sayısının karesi: ", kare)
Bu kodlardaki sorunu anlamaya çalışmadan önce dilerseniz kodları şöyle bir inceleyelim.
Gördüğünüz gibi, burada kullanıcının gireceği sayılara göre karekök ve kare alma işlemleri yapıyoruz. Bu kodlarda gördüğümüz ** işleci yardımıyla bir sayının herhangi bir kuvvetini hesaplayabileceğimizi biliyorsunuz. Mesela 217’nin kaç ettiğini hesaplamak için ** işlecini kullanabiliyoruz:
>>> 21 ** 7
1801088541
Yine bildiğiniz gibi, bu işleçten, bir sayının karesini hesaplamak için de yararlanabiliyoruz. Çünkü neticede bir sayının karesi, o sayının 2. kuvvetidir:
>>> 12 ** 2
144
Aynı şekilde, eğer bir sayının, 0.5’inci kuvvetini hesaplarsak o sayının karekökünü bulmuş oluyoruz. (Bu bilgileri önceki konulardan hatırlıyor olmalısınız):
>>> 144 ** 0.5
12
Kodlarımızı incelediğimize göre, bu programdaki aksaklıkları irdelemeye başlayabiliriz.
Bu program, kullanıcı sayı değerli bir veri girdiği müddetçe sorunsuz bir şekilde çalışacaktır. Peki ya kullanıcı sayı değerli bir veri yerine başka bir şey girerse ne olur?
Örneğin kullanıcı yukarıdaki programa bir sayı yerine, (bilerek veya bilmeyerek) içinde harf barındıran bir veri girerse şuna benzer bir hata alır:
Traceback (most recent call last):
File "deneme.py", line 2, in <module>
karekök = int(veri1) ** 0.5
ValueError: invalid literal for int() with base 10: 'fds'
Yazdığınız programların bu tür hatalar vermesi normaldir. Ancak son kullanıcı açısından düşündüğümüzde, kullanıcının yukarıdaki gibi bir hata mesajı görmesi yerine, hatanın neden kaynaklandığını ya da neyi yanlış yaptığını daha açık bir şekilde ifade eden bir mesaj alması çok daha mantıklı olacaktır. Zira yukarıdaki hata mesajı programcılar açısından anlamlı olabilir, ancak son kullanıcı açısından büsbütün anlaşılmazdır!
Dediğimiz gibi, programınızın çalışma esnasında bu tür hatalar vermesi normal. Çünkü yapmaya çalıştığınız işlem, kullanıcının belli tipte bir veri girmesine bağlı. Burada sizin bir programcı olarak göreviniz, yazdığınız programın çalışma esnasında vermesi muhtemel hataları önceden kestirip, programınızda buna göre bazı önlemler almanızdır. İşte biz de bu bölümde bu önlemleri nasıl alacağımızı anlamaya çalışacağız.
Hata Türleri¶
Biz bu bölümde hatalardan bahsedeceğimizi söylemiştik. Ancak her şeyden önce ‘hata’ kavramının çok boyutlu olduğunu hatırlatmakta fayda var. Özellikle programcılık açısından hata kavramının ne anlama geldiğini biraz incelememiz gerekiyor.
Biz bu bölümde hataları üç farklı başlık altında ele alacağız:
Programcı Hataları (Error)
Program Kusurları (Bug)
İstisnalar (Exception)
Öncelikle programcı hatalarından bahsedelim.
Programcıdan kaynaklanan hatalar doğrudan doğruya programı yazan kişinin dikkatsizliğinden ötürü ortaya çıkan bariz hatalardır. Örneğin şu kod bir programcı hatası içerir:
>>> print "Merhaba Python!"
Bu kodu çalıştırdığınızda şöyle bir hata mesajı görürsünüz:
>>> print "Merhaba Python!"
File "<stdin>", line 1
print "Merhaba Python!"
^
SyntaxError: invalid syntax
Bu hata mesajında bizi ilgilendiren kısım son cümlede yer alıyor:
SyntaxError
, yani Söz dizimi hatası.
Bu hatalar, programlama diline ilişkin bir özelliğin yanlış kullanımından veya
en basit şekilde programcının yaptığı yazım hatalarından kaynaklanır.
Programcının hataları genellikle SyntaxError
şeklinde ortaya çıkar. Bu
hatalar çoğunlukla programcı tarafından farkedilir ve program kullanıcıya
ulaşmadan önce programcı tarafından düzeltilir. Bu tür hataların tespiti diğer
hatalara kıyasla kolaydır. Çünkü bu tür hatalar programınızın çalışmasını
engellediği için bunları farketmemek pek mümkün değildir…
Program kusurları, başka bir deyişle bug’lar ise çok daha karmaşıktır. Kusurlu programlar çoğu zaman herhangi bir hata vermeden çalışır. Ancak programın ürettiği çıktılar beklediğiniz gibi değildir. Örneğin yazdığınız programda bir formül hatası yapmış olabilirsiniz. Bu durumda programınız hiçbir şey yokmuş gibi çalışır, ancak formül hatalı olduğu için hesaplamaların sonuçları yanlıştır. Örneğin daha önceki derslerimizde yazdığımız şu program yukarıdaki gibi bir kusur içerir:
sayı1 = input("Toplama işlemi için ilk sayıyı girin: ")
sayı2 = input("Toplama işlemi için ikinci sayıyı girin: ")
print(sayı1, "+", sayı2, "=", sayı1 + sayı2)
Bu programda kullanıcı veri girdiği zaman, programımız toplama işlemi değil karakter dizisi birleştirme işlemi yapacaktır. Böyle bir program çalışma sırasında hata vermeyeceği için buradaki sorunu tespit etmek, özellikle büyük programlarda çok güçtür. Yani sizin düzgün çalıştığını zannettiğiniz program aslında gizliden gizliye bir bug barındırıyor olabilir.
Aynı şekilde, mesela eval()
fonksiyonunun dikkatsizce kullanıldığı
programlar da güvenlik açısından kusurludur. Yani bu tür programlar bir güvenlik
kusuru (security bug veya security flaw) barındırır.
Dediğimiz gibi, program kusurları çok boyutlu olup, burada anlattığımızdan çok daha karmaşıktır.
Gelelim üçüncü kategori olan istisnalara (exceptions)…
İstisnalar, adından da az çok anlaşılacağı gibi, bir programın çalışması sırasında ortaya çıkan, normalden farklı, istisnai durumlardır. Örneğin şu programa bakalım:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
ilk_sayı = int(ilk_sayı)
ikinci_sayı = int(ikinci_sayı)
print(ilk_sayı, "/", ikinci_sayı, "=", ilk_sayı / ikinci_sayı)
Burada ilk sayıyı ikinci sayıya bölen bir program yazdık. Bu program her türlü bölme işlemini yapabilir. Ama burada hesaba katmamız gereken iki şey var:
Kullanıcı sayı yerine, sayı değerli olmayan bir veri tipi girebilir. Mesela ilk sayıya karşılık 23, ikinci sayıya karşılık ‘fdsfd’ gibi bir şey yazabilir.
Kullanıcı bir sayıyı 0’a bölmeye çalışabilir. Mesela ilk sayıya karşılık 23, ikinci sayıya karşılık 0 yazabilir.
İlk durumda programımız şöyle bir hata verir:
ilk sayı: 23
ikinci sayı: fdsfd
Traceback (most recent call last):
File "deneme.py", line 5, in <module>
ikinci_sayı = int(ikinci_sayı)
ValueError: invalid literal for int() with base 10: 'fdsfd'
Buradaki sorun, sayı değerli olmayan bir verinin, int()
fonksiyonu
aracılığıyla sayıya çevrilmeye çalışılıyor olması.
İkinci durumda ise programımız şöyle bir hata verir:
ilk sayı: 23
ikinci sayı: 0
Traceback (most recent call last):
File "deneme.py", line 7, in <module>
print(ilk_sayı, "/", ikinci_sayı, "=", ilk_sayı / ikinci_sayı)
ZeroDivisionError: division by zero
Buradaki sorun ise, bir sayının 0’a bölünmeye çalışılıyor olması. Matematikte sayılar 0’a bölünemez…
İşte bu iki örnekte gördüğümüz ValueError
ve ZeroDivisionError
birer
istisnadır. Yani kullanıcıların, kendilerinden sayı beklenirken sayı değerli
olmayan veri girmesi veya bir sayıyı 0’a bölmeye çalışması istisnai birer
durumdur ve yazdığımız programların exception (istisna) üretmesine yol açar.
Böylece hata (error), kusur (bug) ve istisna (exception) arasındaki farkları şöyle bir gözden geçirmiş olduk. Yalnız burada şunu söylemekte yarar var: Bu üç kavram arasındaki fark belli belirsizdir. Yani bu kavramların çoğu yerde birbirlerinin yerine kullanıldığını da görebilirsiniz. Örneğin exception kavramı için Türkçe’de çoğu zaman ‘hata’ kelimesini kullanıyoruz. Zaten dikkat ederseniz bu bölümün başlığı da ‘İstisna Yakalama’ değil, ‘Hata Yakalama’dır. Aynı şekilde, İngilizcede de bu kavramların çoğu yerde birbirlerinin yerine kullanıldığını görebilirsiniz. Dolayısıyla, konuya karşı özel bir ilginiz yoksa, hata, kusur ve istisna kavramlarını birbirinden ayırmak için kendinizi zorlamanıza gerek yok. Bu üç kavram çoğu zaman birbirinin yerine kullanılıyor da olsa, aslında aralarında bazı farklar olduğunu öğrenmişseniz bu bölüm amacına ulaşmış demektir.
Konuyla ilgili temel bilgileri edindiğimize göre asıl meseleye geçebiliriz…
try… except…¶
Bir önceki bölümde hatalardan ve hataları yakalamaktan söz ettik. Peki bu hataları nasıl yakalayacağız?
Python’da hata yakalama işlemleri için try... except...
bloklarından
yararlanılır. Hemen bir örnek verelim:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except ValueError:
print("Lütfen sadece sayı girin!")
Biliyoruz ki, bir veriyi sayıya dönüştürmek istediğimizde eğer kullanıcı sayı
değerli bir veri yerine harf değerli bir veri girerse programımız çöker.
Dolayısıyla int(ilk_sayı)
ve int(ikinci_sayı)
kodları, kullanıcının
gireceği veri türüne göre hata üretme potansiyeline sahiptir. O yüzden, burada
hata vereceğini bildiğimiz o kodları try
bloğu içine aldık.
Yine bildiğimiz gibi, veri dönüştürme işlemi sırasında kullanıcının uygun
olmayan bir veri girmesi halinde üretilecek hata bir ValueError
’dır.
Dolayısıyla except
bloğu içine yazacağımız hata türünün adı da
ValueError
olacaktır. O yüzden ValueError
adlı hatayı yakalayabilmek
için şu satırları yazdık:
except ValueError:
print("Lütfen sadece sayı girin!")
Burada bu kodlarla Python’a şu emri vermiş olduk:
Eğer
try
bloğu içinde belirtilen işlemler sırasında birValueError
ile karşılaşırsan bunu görmezden gel ve normal şartlar altında kullanıcıya göstereceğin hata mesajını gösterme. Onun yerine kullanıcıyaLütfen sadece sayı girin!
uyarısını göster.
Yukarıda Türkçeye çevirdiğimiz emri Pythoncada nasıl ifade ettiğimize dikkat edin. Temel olarak şöyle bir yapıyla karşı karşıyayız:
try:
hata verebileceğini bildiğimiz kodlar
except HataAdı:
hata durumunda yapılacak işlem
Gelin isterseniz bir örnek daha verelim.
Hatırlarsanız bir sayının 0’a bölünmesinin mümkün olmadığını, böyle bir durumda programımızın hata vereceğini söylemiştik. Bu durumu teyit etmek için etkileşimli kabukta şu kodu deneyebilirsiniz:
>>> 2 / 0
Bu kod şöyle bir hata mesajı verecektir:
>>> 2 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Daha önce de söylediğimiz gibi, bu hata mesajında bizi ilgilendiren kısım
ZeroDivisionError
. Demek ki bir sayı 0’a bölündüğünde Python
ZeroDivisionError
veriyormuş. O halde şöyle bir kod yazabiliriz:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except ZeroDivisionError:
print("Bir sayıyı 0'a bölemezsiniz!")
Gördüğünüz gibi, Python’ın ZeroDivisionError
vereceğini bildiğimiz durumlara
karşı bu hata türünü yakalama yoluna gidiyoruz. Böylece kullanıcıya anlamsız ve
karmaşık hata mesajları göstermek ve daha da kötüsü, programımızın çökmesine
sebep olmak yerine daha anlaşılır mesajlar üretiyoruz.
Yukarıdaki kodlarda özellikle bir nokta dikkatinizi çekmiş olmalı: Dikkat
ederseniz yukarıdaki kodlar aslında bir değil iki farklı hata üretme
potansiyeline sahip. Eğer kullanıcı sayı değerli veri yerine harf değerli bir
veri girerse ValueError
, eğer bir sayıyı 0’a bölmeye çalışırsa da
ZeroDivisionError
hatası alıyoruz. Peki aynı kodlarda iki farklı hata türünü
nasıl yakalayacağız?
Çok basit:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except ZeroDivisionError:
print("Bir sayıyı 0'a bölemezsiniz!")
except ValueError:
print("Lütfen sadece sayı girin!")
Gördüğünüz gibi çözüm gayet mantıklı. Birden fazla hata türü üreteceğini
bildiğimiz kodları yine tek bir try
bloğu içine alıyoruz. Hata türlerini ise
ayrı except
blokları içinde ele alıyoruz.
Bir program yazarken, en iyi yaklaşım, yukarıda yaptığımız gibi, her hata türü için kullanıcıya ayrı bir uyarı mesajı göstermektir. Böylece kullanıcılarımız bir hatayla karşılaştıklarında sorunu nasıl çözebilecekleri konusunda en azından bir fikir sahibi olabilirler.
Dediğimiz gibi, her hata için ayrı bir mesaj göstermek en iyisidir. Ama tabii dilerseniz hata türlerini gruplayıp hepsi için tek bir hata mesajı göstermeyi de tercih edebilirsiniz. Bunu nasıl yapacağımızı görelim:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except (ValueError, ZeroDivisionError):
print("Bir hata oluştu!")
Gördüğünüz gibi, burada ValueError
ve ZeroDivisionError
adlı hata
türlerini tek bir parantez içinde topladık. Burada dikkat edeceğimiz nokta, bu
hata türlerini gruplarken bunları parantez içine almak ve birbirlerinden
virgülle ayırmaktır.
Bu arada, gördüğünüz gibi yukarıdaki programlar sadece bir kez çalışıp kapanıyor. Ama biz bu programları tekrar tekrar nasıl çalıştırabileceğimizi gayet iyi biliyoruz:
while True:
ilk_sayı = input("ilk sayı (Programdan çıkmak için q tuşuna basın): ")
if ilk_sayı == "q":
break
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except (ValueError, ZeroDivisionError):
print("Bir hata oluştu!")
print("Lütfen tekrar deneyin!")
Python’da hata yakalamanın en yaygın yolu yukarıda gösterdiğimiz gibi kodları
try... except
blokları içine almaktır. Programcılık maceranızın büyük
bölümünde bu yapıyı kullanacaksınız. Ama bazen, karşı karşıya olduğunuz duruma
veya ihtiyacınıza göre try... except
bloklarının farklı varyasyonlarını
kullanmanız gerekebilir. İşte şimdi biz de bu farklı varyasyonların neler
olduğunu incelemeye çalışacağız.
try… except… as…¶
Bildiğiniz gibi, Python bir programın çalışması esnasında hata üretirken çıktıda hata türünün adıyla birlikte kısa bir hata açıklaması veriyor. Yani mesela şöyle bir çıktı üretiyor:
ValueError: invalid literal for int() with base 10: 'f'
Burada ‘ValueError’ hata türünün adı, ‘invalid literal for int() with base 10: ‘f’’ ise hatanın açıklamasıdır. Eğer istersek, yazdığımız programda bu hata açıklamasına erişebiliriz. Dikkatlice bakın:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except ValueError as hata:
print(hata)
Bu programı çalıştırıp sayı değerli olmayan bir veri girersek hata çıktısı şöyle olacaktır:
invalid literal for int() with base 10: 'f'
Gördüğünüz gibi, bu defa çıktıda hata türünün adı (ValueError
) görünmüyor.
Onun yerine sadece hata açıklaması var.
Diyelim ki kullanıcıya olası bir hata durumunda hem kendi yazdığınız hata mesajını, hem de özgün hata mesajını göstermek istiyorsunuz. İşte yukarıdaki yapı böyle durumlarda işe yarayabilir:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except ValueError as hata:
print("Sadece sayı girin!")
print("orijinal hata mesajı: ", hata)
Bu arada, biraz önce yaptığımız gibi, hata türlerini grupladığınızda da bu yöntemi kullanabilirsiniz:
ilk_sayı = input("ilk sayı: ")
ikinci_sayı = input("ikinci sayı: ")
try:
sayı1 = int(ilk_sayı)
sayı2 = int(ikinci_sayı)
print(sayı1, "/", sayı2, "=", sayı1 / sayı2)
except (ValueError, ZeroDivisionError) as hata:
print("Bir hata oluştu!")
print("orijinal hata mesajı: ", hata)
Burada except falancaHata as filanca
yapısını kullanarak falancaHata
’yı
filanca olarak isimlendiriyor ve daha sonra bu ismi istediğimiz gibi
kullanabiliyoruz. Böylece bütün hata türleri için hem kendi yazdığınız mesajı
görüntüleyebiliyor, hem de özgün hata mesajını da çıktıya eklediğimiz için,
kullanıcıya hata hakkında en azından bir fikir sahibi olma imkanı vermiş
oluyoruz.
try… except… else…¶
Daha önce de dediğimiz gibi, Python’da hata yakalama işlemleri için çoğunlukla
try... except...
bloklarını bilmek yeterli olacaktır. İşlerimizin büyük
kısmını sadece bu blokları kullanarak halledebiliriz. Ancak Python bize bu
konuda, zaman zaman işimize yarayabilecek başka araçlar da sunmaktadır. İşte
try... except... else...
blokları da bu araçlardan biridir. Bu bölümde
kısaca bu blokların ne işe yaradığından söz edeceğiz.
Öncelikle try... except... else...
bloğunun ne işe yaradığına bakalım.
Esasında biz bu else
deyimini daha önce de ‘koşullu ifadeler’ konusunu
işlerken görmüştük. Buradaki kullanımı da zaten hemen hemen aynıdır. Diyelim ki
elimizde şöyle bir şey var:
try:
bölünen = int(input("bölünecek sayı: "))
bölen = int(input("bölen sayı: "))
print(bölünen/bölen)
except ValueError:
print("hata!")
Burada eğer kullanıcı sayı yerine harf girerse ValueError
hatası alırız. Bu
hatayı except ValueError:
ifadesiyle yakalıyoruz ve hata verildiğinde
kullanıcıya bir mesaj göstererek programımızın çökmesini engelliyoruz. Ama
biliyoruz ki, bu kodları çalıştırdığımızda Python’ın verebileceği tek hata
ValueError
değildir. Eğer kullanıcı bir sayıyı 0’a bölmeye çalışırsa Python
ZeroDivisionError
adlı hatayı verecektir. Dolayısıyla bu hatayı da yakalamak
için şöyle bir şey yazabiliriz:
try:
bölünen = int(input("bölünecek sayı: "))
bölen = int(input("bölen sayı: "))
print(bölünen/bölen)
except ValueError:
print("Lütfen sadece sayı girin!")
except ZeroDivisionError:
print("Bir sayıyı 0'a bölemezsiniz!")
Bu şekilde hem ValueError
hatasını hem de ZeroDivisionError
hatasını
yakalamış oluruz. Bu kodların özelliği, except...
bloklarının tek bir
try...
bloğunu temel almasıdır. Yani biz burada bütün kodlarımızı tek bir
try...
bloğu içine tıkıştırıyoruz. Bu blok içinde gerçekleşen hataları da
daha sonra tek tek except...
blokları yardımıyla yakalıyoruz. Ama eğer biz
istersek bu kodlarda verilebilecek hataları gruplamayı da tercih edebiliriz:
try:
bölünen = int(input("bölünecek sayı: "))
bölen = int(input("bölen sayı: "))
except ValueError:
print("Lütfen sadece sayı girin!")
else:
try:
print(bölünen/bölen)
except ZeroDivisionError:
print("Bir sayıyı 0'a bölemezsiniz!")
Burada yaptığımız şey şu: İlk try... except...
bloğu yardımıyla öncelikle
int(input())
fonksiyonu ile kullanıcıdan gelecek verinin sayı olup
olmadığını denetliyoruz. Ardından bir else...
bloğu açarak, bunun içinde
ikinci try... except...
bloğumuzu devreye sokuyoruz. Burada da bölme
işlemini gerçekleştiriyoruz. Kullanıcının bölme işlemi sırasında 0 sayısını
girmesi ihtimaline karşı da except ZeroDivisionError
ifadesi yardımıyla
olası hatayı göğüslüyoruz. Bu şekilde bir kodlamanın bize getireceği avantaj,
hatalar üzerinde belli bir kontrol sağlamamıza yardımcı olmasıdır. Yukarıdaki
kodlar sayesinde hatalara bir nevi ‘teker teker gelin!’ mesajı vermiş oluyoruz.
Böylelikle her blok içinde sadece almayı beklediğimiz hatayı karşılıyoruz.
Mesela yukarıda ilk try...
bloğu içindeki dönüştürme işlemi yalnızca
ValueError
hatası verebilir. else:
bloğundan sonraki try...
bloğunda
yer alan işlem ise ancak ZeroDivisionError
verecektir. Biz yukarıda
kullandığımız yapı sayesinde her bir hatayı tek tek ve yeri geldiğinde
karşılıyoruz. Bu durumun aksine, bölümün ilk başında verdiğimiz try...
except
bloğunda hem ValueError
hem de ZeroDivisionError
hatalarının
gerçekleşme ihtimali bulunuyor. Dolayısıyla biz orada bütün hataları tek bir
try...
bloğu içine sıkıştırmış oluyoruz. İşte else:
bloğu bu sıkışıklığı
gidermiş oluyor. Ancak sizi bir konuda uyarmak isterim: Bu yapı, her akla
geldiğinde kullanılacak bir yapı değildir. Büyük programlarda bu tarz bir
kullanım kodlarınızın darmadağın olmasına, kodlarınız üzerindeki denetimi
tamamen kaybetmenize de yol açabilir. Sonunda da elinizde bölük pörçük bir kod
yığını kalabilir. Zaten açıkça söylemek gerekirse try... except... else...
yapısının çok geniş bir kullanım alanı yoktur. Bu yapı ancak çok nadir
durumlarda kullanılmayı gerektirebilir. Dolayısıyla bu üçlü yapıyı hiç
kullanmadan bir ömrü rahatlıkla geçirebilirsiniz.
try… except… finally…¶
try... except... else...
yapılarının dışında, Python’ın bize sunduğu bir
başka yapı da try... except... finally...
yapılarıdır. Bunu şöyle
kullanıyoruz:
try:
...bir takım işler...
except birHata:
...hata alınınca yapılacak işlemler...
finally:
...hata olsa da olmasa da yapılması gerekenler...
finally..
bloğunun en önemli özelliği, programın çalışması sırasında
herhangi bir hata gerçekleşse de gerçekleşmese de işletilecek olmasıdır. Eğer
yazdığınız programda mutlaka ama mutlaka işletilmesi gereken bir kısım varsa, o
kısmı finally...
bloğu içine yazabilirsiniz.
finally...
bloğu özellikle dosya işlemlerinde işimize yarayabilir. Henüz
Python’da dosyalarla nasıl çalışacağımızı öğrenmedik, ama ben şimdilik size en
azından dosyalarla çalışma prensibi hakkında bir şeyler söyleyeyim.
Genel olarak Python’da dosyalarla çalışabilmek için öncelikle bilgisayarda
bulunan bir dosyayı okuma veya yazma kipinde açarız. Dosyayı açtıktan sonra bu
dosyayla ihtiyacımız olan birtakım işlemler gerçekleştiririz. Dosyayla işimiz
bittikten sonra ise dosyamızı mutlaka kapatmamız gerekir. Ancak eğer dosya
üzerinde işlem yapılırken bir hata ile karşılaşılırsa dosyamızı kapatma işlemini
gerçekleştirdiğimiz bölüme hiç ulaşılamayabilir. İşte finally...
bloğu böyle
bir durumda işimize yarayacaktır:
try:
dosya = open("dosyaadı", "r")
...burada dosyayla bazı işlemler yapıyoruz...
...ve ansızın bir hata oluşuyor...
except IOError:
print("bir hata oluştu!")
finally:
dosya.close()
Burada finally...
bloğu içine yazdığımız dosya.close()
ifadesi dosyamızı
güvenli bir şekilde kapatmaya yarıyor. Bu blok, yazdığımız program hata verse de
vermese de işletilecektir.
raise¶
Bazen, yazdığımız bir programda, kullanıcının yaptığı bir işlem normal şartlar
altında hata vermeyecek olsa bile biz ona ‘Python tarzı’ bir hata mesajı
göstermek isteyebiliriz. Böyle bir durumda ihtiyacımız olan şey Python’ın bize
sunduğu raise
adlı deyimdir. Bu deyim yardımıyla duruma özgü hata mesajları
üretebiliriz. Bir örnek verelim:
bölünen = int(input("bölünecek sayı: "))
if bölünen == 23:
raise Exception("Bu programda 23 sayısını görmek istemiyorum!")
bölen = int(input("bölen sayı: "))
print(bölünen/bölen)
Burada eğer kullanıcı 23 sayısını girerse, kullanıcıya bir hata mesajı
gösterilip programdan çıkılacaktır. Biz bu kodlarda Exception
adlı genel
hata mesajını kullandık. Burada Exception
yerine her istediğimizi yazamayız.
Yazabileceklerimiz ancak Python’da tanımlı hata mesajları olabilir. Örneğin
NameError
, TypeError
, ZeroDivisionError
, IOError
, vb…
Bir örnek verelim:
tr_karakter = "şçğüöıİ"
parola = input("Parolanız: ")
for i in parola:
if i in tr_karakter:
raise TypeError("Parolada Türkçe karakter kullanılamaz!")
else:
pass
print("Parola kabul edildi!")
Bu kodlar çalıştırıldığında, eğer kullanıcı, içinde Türkçe karakter geçen bir
parola yazarsa kendisine TypeError
tipinde bir hata mesajı gösteriyoruz.
Eğer kullanıcının parolası Türkçe karakter içermiyorsa hiçbir şey yapmadan
geçiyoruz ve bir sonraki satırda kendisine ‘Parola kabul edildi!’ mesajını
gösteriyoruz.
raise
deyimini, bir hata mesajına ek olarak bir işlem yapmak istediğimizde
de kullanabiliriz. Örneğin:
try:
bölünen = int(input("bölünecek sayı: "))
bölen = int(input("bölen sayı: "))
print(bölünen/bölen)
except ZeroDivisionError:
print("bir sayıyı 0'a bölemezsiniz")
raise
Burada, eğer kullanıcı bir sayıyı 0’a bölmeye çalışırsa, normal bir şekilde
ZeroDivisionError
hatası verilecek ve programdan çıkılacaktır. Ama bu hata
mesajıyla birlikte kullanıcıya ‘bir sayıyı 0’a bölemezsiniz,’ uyarısını da
gösterme imkanını elde edeceğiz. Yani burada except ZeroDivisionError
bloğunu herhangi bir hatayı engellemek için değil, hataya ilave bilgi eklemek
için kullanıyoruz. Bunu yapmamızı sağlayan şey tabii ki bu kodlar içinde görünen
raise
adlı deyimdir…
assert¶
Bazen programımızda bir hata yaptığımızda bu hatayı bulmakta zorlanabiliriz.
print
fonksiyonunu kullanarak bu hatamızı bulmaya çalışabiliriz ancak
bu da programımız ekrana çok fazla yazdırma işlemi yapıyorsa gözden kaçabilir.
Böyle durumlarda assert
ifadesini kullanabiliriz. assert
ifadesi
aynı zamanda hata yükseltmenin kısa bir yoludur. Ancak assert
ifadesini
kullanarak sadece AssertionError
türünde bir hata yükseltebiliriz. Normalde
raise
kullanmamız daha doğru olacaktır. Dediğimiz gibi assert
ifadesi
hızlı bir şekilde kodumuzdaki hataları belirlemek için kullanılır.
Şimdi şöyle bir kodumuz olduğunu düşünelim:
giriş = input("Merhaba! Adın ne? ")
if len(giriş) == 0:
raise AssertionError("İsim bölümü boş.")
print("Hoşgeldiniz.")
Bu kodu assert
kullanarak şu şekilde de yazabilirdik:
giriş = input("Merhaba! Adın ne? ")
assert len(giriş) != 0 , "İsim bölümü boş."
print("Hoşgeldiniz.")
Dikkat ederseniz assert
ifadesinin şu şekilde kullanıldığını görebilirsiniz:
assert ifade , mesaj
Burada ifade
bir bool
yani True
veya False
olabileceği gibi, (aynı if
’deki gibi)
bool
fonksiyonu ile birlikte kullanılabilecek bir nesne de olabilir. Sonuç olarak
eğer ifade
’nin değeri True
ise assert
ifademiz çalışmayacak, False
ise
çalışacaktır. Yani assert
ifademizin içine doğru olmasını istediğimiz durumu yazmalıyız ki
eğer yanlış olursa hata yükseltsin. Zaten assert
kelimesi “iddia etmek” anlamına gelir. Yani biz assert
ifadesini kullanarak bir ifadenin doğru olduğunu iddia ediyoruz, bu iddiamız yanlış ise Python bir hata yükseltiyor. Ayrıca assert
ifademiz çalıştığında bir hata yükseleceği için program da sonlanacaktır, yani bu özelliği istemediğimiz bir durum gerçekleştiğinde
programı sonlandırmak için de kullanabiliriz. mesaj
ise hata verildiğinde ekrana yazılmasını
istediğimiz mesajdır. Tabii ki raise
ifadesinde olduğu gibi burada da bir mesaj vermek zorunda
değiliz. Yani assert
ifadesini şu şekilde de kullanabiliriz:
assert ifade
Şimdi baştaki örneğimize geri dönersek:
giriş = input("Merhaba! Adın ne? ")
assert len(giriş) != 0 , "İsim bölümü boş."
print("Hoşgeldiniz.")
Bu kodumuzu çalıştırdığımızda:
Merhaba! Adın ne? Ali
Hoşgeldiniz.
>>>
giriş
değişkenimizin uzunluğu 0
olmadığı için bir hata verilmedi. Şimdi
aynı kodu çalıştırıp hiçbir şey yazmadan enter
tuşuna basalım:
.. code-block:: pycon
>>> Merhaba! Adın ne?
Traceback (most recent call last):
File "C:\Users\Kullanıcı\Desktop\assert_ifadesi.py", line 2, in <module>
assert len(giriş) != 0 , "İsim bölümü boş."
AssertionError: İsim bölümü boş.
Gördüğünüz gibi assert
ifadesini de bu şeklide kullanıyoruz. Burada bir şeye
dikkat etmek lazım ki assert
bir fonksiyon değildir, bu yüzden parantezler
ile şu şekilde kullanılmamalıdır:
assert(ifade)
Bu kullanım hata vermeyecek olsa da farkında olmadan -ileride öğreneceğimiz bir konu- bir demet oluşturmuş oluyoruz.
Son olarak büyük bir proje yazdığımızda, şu örnekteki gibi:
giriş = input("Merhaba! Adın ne? ")
if len(giriş) == 0:
raise AssertionError("İsim bölümü boş.")
print("Hoşgeldiniz.")
if
ve raise
ifadesi kullanmak yerine assert
kullanmamızın iki avantajından bahsedelim. Öncelikle istediğiniz zaman geliştirme arayüzünüzün “Bul (Find)” özelliğini kullanarak bu anahtar kelimeleri
bulup silebilir veya yorum satırı haline getirebilirsiniz. Bu özellik uzun bir kod yazdığımızda zaman kazanmamızı sağlar.
İkinci olarak da python yorumlayıcısını komut satırından -O
parametresi (optimize anlamına gelir) ile şu şekilde çağırdığımızda:
C:\Users\User> python -O kod_dosyası
kod_dosyası
programımızdaki bütün assert
ifadeleri python yorumlayıcısı tarafından yok sayılır ve hiçbiri çalıştırılmaz. Yani assert
ifadelerinin hepsini yorum satırı haline getirmek istediğimizde bunun ile uğraşmak yerine bu özelliği kullanabiliriz.
Konu ile alakalı daha çok örnek için buraya bakabilirsiniz.
Bütün Hataları Yakalamak¶
Şimdiye kadar yaptığımız bütün örneklerde except...
bloğunu bir hata mesajı
adıyla birlikte kullandık. Yani örneklerimiz şuna benziyordu:
try:
....birtakım işler...
except ZeroDivisionError:
...hata mesajı...
Yukarıdaki kod yardımıyla sadece ZeroDivisionError
adlı hatayı
yakalayabiliriz. Eğer yazdığımız program başka bir hata daha veriyorsa, o hata
mesajı yukarıdaki blokların kapsamı dışında kalacaktır. Ama eğer istersek
yukarıdaki kodu şu şekilde yazarak olası bütün hataları yakalayabiliriz:
try:
....birtakım işler...
except:
...hata mesajı...
Gördüğünüz gibi, burada herhangi bir hata adı belirtmedik. Böylece Python, yazdığımız programda hangi hata oluşursa oluşsun hepsini yakalayabilecektir.
Bu yöntem gözünüze çok pratik görünmüş olabilir, ama aslında hiç de öyle sayılmaz. Hatta oldukça kötü bir yöntem olduğunu söyleyebiliriz bunun. Çünkü bu tarz bir kod yazımının bazı dezavantajları vardır. Örneğin bu şekilde bütün hata mesajlarını aynı kefeye koyarsak, programımızda ne tür bir hata oluşursa oluşsun, kullanıcıya hep aynı mesajı göstermek zorunda kalacağız. Bu da, herhangi bir hata durumunda kullanıcıyı ne yapması gerektiği konusunda doğru düzgün bilgilendiremeyeceğimiz anlamına geliyor. Yani kullanıcı bir hataya sebep olduğunda tersliğin nereden kaynaklandığını tam olarak kestiremeyecektir.
Ayrıca, eğer kendimiz bir program geliştirirken sürekli olarak bu tarz bir
yazımı benimsersek, kendi kodlarımızdaki hataları da maskelemiş oluruz.
Dolayısıyla, Python yukarıdaki geniş kapsamlı except...
bloğu nedeniyle
programımızdaki bütün hataları gizleyeceği için, programımızdaki potansiyel
aksaklıkları görme imkanımız olmaz. Dolayısıyla bu tür bir yapıdan olabildiğince
kaçınmakta fayda var. Ancak elbette böyle bir kod yazmanızı gerektiren bir
durumla da karşılaşabilirsiniz. Örneğin:
try:
birtakım kodlar
except ValueError:
print("Yanlış değer")
except ZeroDivisionError:
print("Sıfıra bölme hatası")
except:
print("Beklenmeyen bir hata oluştu!")
Burada olası bütün hata türlerini yakaladıktan sonra, bunların dışında bizim o
anda öngöremediğimiz bir hatanın oluşması ihtimaline karşı except:
kodunu
kullanarak kullanıcıya genel bir hata mesajı göstermeyi tercih edebiliriz.
Böylece beklenmeyen bir hata meydana gelmesi durumunda da programımız çökmek
yerine çalışmaya devam edebilecektir.
Örnek Uygulama¶
Hata yakalama konusunu bütün ayrıntılarıyla inceledik. Gelin şimdi isterseniz ufak bir örnek yapalım.
Hatırlarsanız bir kaç bölüm önce şöyle bir uygulama yazmıştık:
import sys
_2x_metni = """
Python'ın 2.x sürümlerinden birini kullanıyorsunuz.
Programı çalıştırabilmek için sisteminizde Python'ın
3.x sürümlerinden biri kurulu olmalı."""
_3x_metni = "Programa hoşgeldiniz."
if sys.version_info.major < 3:
print(_2x_metni)
else:
print(_3x_metni)
Bu programın ne iş yaptığını biliyorsunuz. Bu program yardımıyla, kullanıcılarımızın bilgisayarlarındaki Python sürümünü kontrol edip, programımızın kullanılan sürüme göre tepki vermesini sağlıyoruz.
Ancak burada çok ciddi bir problem var. Python’ın 2.7 öncesi sürümlerinde sys
modülünün version_info()
metodu farklı çıktılar verir. Mesela Python’ın 2.7
öncesi sürümlerinde version_info()
metodunun major, minor veya micro
gibi nitelikleri bulunmaz. Bu nitelikler Python programlama diline 2.7 sürümüyle
birlikte geldi. Dolayısıyla yukarıdaki programı Python’ın 2.7 öncesi
sürümlerinden biriyle çalıştıran kullanıcılarınız istediğiniz çıktıyı
alamayacak, Python bu kullanıcalara şuna benzer bir hata mesajı göstererek
programın çökmesine sebep olacaktır:
AttributeError: 'tuple' object has no attribute 'major'
Python’ın 2.7 öncesi sürümlerinin kurulu olduğu bilgisayarlarda da programınızın en azından çökmemesi ve makul bir çıktı verebilmesi için yukarıdaki kodlar şöyle yazabilirsiniz:
import sys
_2x_metni = """
Python'ın 2.x sürümlerinden birini kullanıyorsunuz.
Programı çalıştırabilmek için sisteminizde Python'ın
3.x sürümlerinden biri kurulu olmalı."""
_3x_metni = "Programa hoşgeldiniz."
try:
if sys.version_info.major < 3:
print(_2x_metni)
else:
print(_3x_metni)
except AttributeError:
print(_2x_metni)
Gördüğünüz gibi, AttributeError
adlı hatayı vereceğini bildiğimiz kısmı bir
try... except
bloğu içine aldık. Eğer programımız AttributeError
hatasını veriyorsa, programımızın çalıştırıldığı sistem Python’ın 2.7 sürümünden
daha düşük bir sürümü kullanıyor demektir. O yüzden kullanıcıya _2x_metni’ni
gösteriyoruz.
Elbette yukarıdaki programı yazmanın çok daha düzgün yolları vardır. Ama biz
hata yakalama yöntemlerinin buna benzer durumlarda da bir alternatif olarak
kullanılabileceğini bilelim. Ayrıca, dediğimiz gibi, try... except
blokları
yukarıdaki sorunun çözümü için en uygun araçlar olmasa da, bazı durumlarda
hatayı önlemenin makul tek yoludur.
Önemli Not
Sorularınızı yorumlarda dile getirmek yerine Yazbel Forumunda sorarsanız çok daha hızlı cevap alabilirsiniz.Belgelerdeki bir hata veya eksiği dile getirecekseniz lütfen yorumları kullanmak yerine Github'da bir konu (issue) açın.
Eğer yazdığınız yorum içinde kod kullanacaksanız kodlarınızı <pre><code> etiketleri içine alın. Örneğin:
<pre><code class="python"> print("Merhaba Dünya!") </code></pre>