18hata yakalama.fw min

Python Ders 18 | Hata Yakalama

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 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'

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 hataları üç farklı başlık altında ele alacağız:

  1. Programcı Hataları (Error)
  2. Program Kusurları (Bug)
  3. İstisnalar (Exception)

Öncelikle programcı hatalarından bahsedelim.

Ö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, programcının yaptığı yazım hatalarından kaynaklanır.

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. 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. Yani sizin düzgün çalıştığını zannettiğiniz program aslında gizliden gizliye bir bug barındırıyor olabilir.

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. Ama burada hesaba katmamız gereken iki şey var:

  1. 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.
  2. 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.

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!")

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 bir ValueError 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ıya Lü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 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.

Yukarıdaki kodlarda bir değil iki farklı hata üretme potansiyeline sahip. 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!")

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.

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!")

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'

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)

try… except… else…

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.

Öncelikle try... except... else... bloğunun ne işe yaradığına bakalım. 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!")

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.

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.

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.

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()

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)

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 NameErrorTypeErrorZeroDivisionErrorIOError, 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!")

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

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.

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 .

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

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)

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

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.

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!")

Bu eğitim seti Kaynak tarafından oluşturulmuştur.

İletişim: admin@herseymi.com
Yazı oluşturuldu 96

Bir Yorum Yazın

Benzer yazılar

Aramak istediğinizi üstte yazmaya başlayın ve aramak için enter tuşuna basın. İptal için ESC tuşuna basın.

Üste dön