Ayberk Seven

Ana Dal: Bilgisayar Mühendisliği

Yan Dal: Mekatronik Mühendisliği

Ayberk Seven

Ana Dal: Bilgisayar Mühendisliği

Yan Dal: Mekatronik Mühendisliği

Blog Yazılarım

İstisna Durumları ve Hataları Yönetmek(Exception Handling)

Python’da hatalar 2 şekilde karşımıza çıkarlar; Yazım(Syntax) Hataları ve İstisnalar. Her ne şekilde olursa olsun uygulamanızda hatalar ile karşılaşmak son derece büyük bir problemdir, çünkü programınızın çalışmasını durdurur.

Yazım hatalarına örnek olarak tanımlamadığınız bir değişkeni kullanmak ya da bir fonksiyon yazarken alt satıra geçmeden önce iki nokta üst üste koymayı unutmak gibi dikkatsizlik sonucu ortaya çıkabilecek hatalar verilebilir.

print(x)

Çıktı:

NameError: name 'x' is not defined

Gördüğünüz gibi x değişkenini henüz tanımlamadan ekrana yazdırmayı denedik ve aldığımız sonuç hata ile sonlanan bir uygulama oldu.

def toplama(sayi1, sayi2)
  return sayi1 + sayi2

Çıktı:

SyntaxError: invalid syntax

Burada da, fonksiyon tanımladıktan sonra iki nokta üst üste koymadan alt satıra geçtik. Sonuç yine trajik bir şekilde uygulamamızın hata ile sonlandırılması oldu.

İşin güzel yanı şu ki, genelde hata mesajları yaptığınız hatayı detaylı ve anlamlı bir şekilde size bildirdiği için bu hatayı düzeltme imkanınız olur. Ancak her ne kadar hata mesajları size düzeltme fırsatı verse de, yayınlanmış bir uygulamada kullanıcılarınızın bu hatalar ile karşılaşmasını ve uygulamanızın sonlandırılmasını istemezsiniz.

Bunun için; hata ayıklama(debugging), hata yönetimi(error handling) ve yazılım testleri gibi yöntemler geliştirilmiştir.

İstisnalar ise yine hata kapsamındadır ancak burada unutma ya da dikkatsizlikten ziyade, kod yazdığınız konuya tam hakim olmamaktan kaynaklanabilecek tüm sonuçları düşünememe durumları ile karşılaşırsınız.

Örneğin matematiksel bir hesaplamada bölme işlemini kullandığınızı düşünelim. Basit bir hesap makinesi uygulamasında iki sayının bölümünü bulmak için kullanıcıdan veri alalım;

def bolme():
  sayi1=input("Lütfen ilk sayıyı giriniz: ")
  sayi2=input("Lütfen ikinci sayıyı da giriniz: ")

  print(int(sayi1)/int(sayi2))

bolme()

Kullanıcının 234 ve 9 sayılarını girdiğini varsayalım;

Çıktı:

Lütfen ilk sayıyı giriniz: 234
Lütfen ikinci sayıyı da giriniz: 9
26.0

Peki kullanıcı sayıları şu şekilde seçerse;

Çıktı:

Lütfen ilk sayıyı giriniz: 234
Lütfen ikinci sayıyı da giriniz: 0
Traceback (most recent call last):
  File "main.py", line 17, in <module>
    bolme()
  File "main.py", line 15, in bolme
    print(int(sayi1)/int(sayi2))
ZeroDivisionError: division by zero

Bu karşılaşabileceğiniz bir durumdur. Kullanıcı ikinci sayı  olarak sıfır değerini girebilir. Ancak bölme işleminde sıfıra bölme tanımsızdır. Öyleyse uygulamanızın hata vererek çalışmasına engel olacak bu istina durumunu kontrol edebilmeniz gerekir.

İşte programlama dillerinde bu imkanı size tanıyan yapı İstisna Yönetimi(Exception Handling)dir.

İstisna ile karşılaşabileceğiniz bu gibi durumlarda ya da harici veriler ile çalışırken kaynağın cevap verip vermeyeceğinden emin olamayacağınız için karşılaşmanız muhtemel sorunlarla baş edebilmek için Python size istisna yönetimi gerçekleştirebilmeniz için tıpkı if koşullu ifadelerine benzer şekilde kullanılan try-except koşullu ifadelerini sunar.

def bolme():
  sayi1=input("Lütfen ilk sayıyı giriniz: ")
  sayi2=input("Lütfen ikinci sayıyı da giriniz: ")

  print(int(sayi1)/int(sayi2))

try:
  bolme()
except:
  print("Bir hata ile karşılaşıldı. Lütfen tekrar deneyiniz.")

print()
print("Uygulamanın işleyişi sonlandırılmadı. Kodlar çalıştırılmaya devam ediyor.")

Çıktı:

Lütfen ilk sayıyı giriniz: 234
Lütfen ikinci sayıyı da giriniz: 0
Bir hata ile karşılaşıldı. Lütfen tekrar deneyiniz.

Uygulamanın işleyişi sonlandırılmadı. Kodlar çalıştırılmaya devam ediyor.

Burada gördüğünüz gibi try bloğu, bir kod bloğunu hatalar için test etmenizi sağlar. Dilerseniz kodu direkt try bloğu içine yazabileceğiniz gibi yukarıda yer alan örnekteki şekilde dışarıda tanımlanan fonksiyonları try bloğu içinde de çalıştırabilirsiniz.  except bloğu ise, hata ortaya çıktığında çalıştıracağınız kodları yazacağınız alandır. Eğer kullanıcının yapabileceği hatayı tahmin edebiliyorsanız ya da biliyorsanız buna uygun bir işlem gerçekleştirebilirsiniz. Ya da örneğimizde olduğu gibi bir hata mesajı görüntüleyip kullanıcıyı bilgilendirebilirsiniz. Yukarıdaki örnekte, kullanıcı yanlış veri girdiği için yeniden veri girmesi amacıyla hata mesajından sonra fonksiyonumuza except bloğu içinde tekrar çağrı yaparak doğru veri girmesi için zorlayabilirdik. İstisna Yönetimi(Exception Handling) kullandığınızda hata uygulamanızı sonlandırmayacağı için kullanıcı doğru kullanımı yapana kadar aynı kodu işletebilir. Ancak size hatanın programın işleyişini bozmadığını göstermek için bu kodu eklemedik. Eğer hatalar uygulamanızın akışını etkilemiyorsa İstisna Yönetimi(Exception Handling) diğer adımlara geçmenize izin veren bir yapıdır.

Dilerseniz birden fazla except bloğu kullanmanız da mümkün. Bir önceki paragrafta “Eğer kullanıcının yapabileceği hatayı tahmin edebiliyorsanız ya da biliyorsanız buna uygun bir işlem gerçekleştirebilirsiniz.” demiştik. Python dokümanlarında İstisna Yönetimi(Exception Handling) konusunda İstisna Hiyerarşisi(Exception Hierarchy) başlığı altında karşılaşılabilecek hataların bir listesini görebilirsiniz. Uygulamanızla ilişkili olabilecek bu hatalar için özelleştirilmiş kodlar yazabilir, beklenmedik durumlar için hata mesajı görüntüleyebilirsiniz.

Yukarıdaki örneğimizi şu şekilde yazabiliriz;

def bolme():
  sayi1=input("Lütfen ilk sayıyı giriniz: ")
  sayi2=input("Lütfen ikinci sayıyı da giriniz: ")

  print(int(sayi1)/int(sayi2))

try:
  bolme()
except ZeroDivisionError:
  print("Bölme işleminde bölen sıfır olamaz. Lütfen tekrar deneyiniz.")
  bolme()
except:
  print("Beklenmeyen bir hata ile karşılaşıldı. Lütfen destek ekibini bilgilendiriniz.")

print()
print("Uygulamanın işleyişi sonlandırılmadı. Kodlar çalıştırılmaya devam ediyor.")

Çıktı:

Lütfen ilk sayıyı giriniz: 234
Lütfen ikinci sayıyı da giriniz: 0
Bölme işleminde bölen sıfır olamaz. Lütfen tekrar deneyiniz.
Lütfen ilk sayıyı giriniz: 234
Lütfen ikinci sayıyı da giriniz: 18
13.0

Uygulamanın işleyişi sonlandırılmadı. Kodlar çalıştırılmaya devam ediyor.

Gördüğünüz gibi hem kullanıcıyı yaptığı hatayı düzeltmeye yönlendirdik, hem de uygulamamız sonlandırılmadığı için başarılı şekilde iş akışına devam etti.

Eğer Python dokümanlarında İstisna Hiyerarşisi(Exception Hierarchy) başlığında yer alan bildiğimiz hatalardan aynı anda birden fazla ortaya çıkması söz konusuysa her biri için ayrı ayrı except bloğu yazmak yerine bir arada ifade edebiliriz. Örneğin kullanıcıdan veri girişi alarak matematiksel işlem gerçekleştirdiğimiz bölme işleminde kullanıcı bölen rakam değeri olarak 0 girerek hata yapabileceği gibi, tuş basarken yanlışlıkla rakam yerine metin de girebilir. Her iki hatayı bira arada yönetmek için kodumuzu şu şekilde de yazabiliriz;

def bolme():
  sayi1=input("Lütfen ilk sayıyı giriniz: ")
  sayi2=input("Lütfen ikinci sayıyı da giriniz: ")

  print(int(sayi1)/int(sayi2))

try:
  bolme()
except (ZeroDivisionError,ValueError):
  print("Hatalı bir veri girişi yaptınız ya da bölen olarak sıfır girdiniz. Lütfen tekrar deneyiniz.")
  bolme()
except:
  print("Beklenmeyen bir hata ile karşılaşıldı. Lütfen destek ekibini bilgilendiriniz.")

Çıktı:

Lütfen ilk sayıyı giriniz: 2w3
Lütfen ikinci sayıyı da giriniz: 2
Hatalı bir veri girişi yaptınız ya da bölen olarak sıfır girdiniz. Lütfen tekrar deneyiniz.
Lütfen ilk sayıyı giriniz: 234
Lütfen ikinci sayıyı da giriniz: 2
117.0

Uygulamanın işleyişi sonlandırılmadı. Kodlar çalıştırılmaya devam ediyor.

Try-Except yapısında kullanabileceğiniz başka öntanımlı ifadeler de mevcuttur. Tıpkı if koşullu ifadelerinde olduğu gibi try-except koşullu ifadelerine de else kod bloğunu yereleştirebilirsiniz. Bu alan eğer herhangi bir hata ile karşılaşılmaz ise yürütülecek olan işlemlere ait kodları içerir. Örneklemek için yukarıdaki kodumuzu farklı bir yaklaşımla try-except-else kullanacak şekilde yazalım;

try:
  sayi1=input("Lütfen ilk sayıyı giriniz: ")
  sayi2=input("Lütfen ikinci sayıyı da giriniz: ") 
  sonuc=int(sayi1)/int(sayi2)
except ZeroDivisionError:
  print("Bölme işleminde bölen sıfır olamaz. Lütfen tekrar deneyiniz.")
  bolme()
except:
  print("Beklenmeyen bir hata ile karşılaşıldı. Lütfen destek ekibini bilgilendiriniz.")
else:
  print(sonuc)

Çıktı:

Lütfen ilk sayıyı giriniz: 234
Lütfen ikinci sayıyı da giriniz: 36
6.5

Örnekler arasında bütünlük sağlamak için aynı örneği her yeni öğrendiğimiz özellikle birlikte kullandık,  “bu örnekte else’in kullanılmasına ne gerek vardı, kullanmadan da gayet iyi bir şekilde çalışıyor” diye düşünebilirsiniz haklı olarak. Ancak else kullanımı bazı alanlarda oldukça etkili olabilir. Örneğin online bir servis ile çalışıyorsanız, mesela bir API’den veri çekiyor ve uygulamanızda kullanıyorsanız, mutlaka try-except bloklarını kullanmalısınız, çünkü API gibi harici bir veri kaynağının her durumda erişilebilir olduğundan emin olamazsınız. Bu tür uygulamalar hata verme olasılığı yüksek olan işlemler gerçekleştirir. Bu durumda, try bloğuna API bağlantısını  sağlayan kodları, except bloğuna bağlantı kurulamazsa çalıştıracağınız kodları, else bloğuna ise bağlantı sağlandığında yapacağınız işlemler ile ilgili kodları yazabilirsiniz.

Bunların dışında try-except yapısıyla birlikte, bir de finally bloğu kullanabilirsiniz. Bu blok try bloğunun hata üretip üretmemesine bakılmaksızın çalıştırılır.

try:
  bolme()
except:
  print("Beklenmeyen bir hata ile karşılaşıldı. Lütfen destek ekibini bilgilendiriniz.")
finally:
  print("Hata olsun olmasın her koşulda çalıştırılacak bir mesajınız var!\nBizimle olduğunuz için teşekkür ederiz...")
Çıktı:

Lütfen ilk sayıyı giriniz: 123
Lütfen ikinci sayıyı da giriniz: 0
Beklenmeyen bir hata ile karşılaşıldı. Lütfen destek ekibini bilgilendiriniz.
Hata olsun olmasın her koşulda çalıştırılacak bir mesajınız var!
Bizimle olduğunuz için teşekkür ederiz...

Finally bloğu çalıştırılmasını istediğiniz son kodlarınızı içerir. Örneğin, bir dosya açtığınızı ve yazma haklarınızın olmamasına rağmen veri eklemeye çalıştığınızı düşünelim. Bu durumda exept bloğu çalıştırılıp program akışı sonraki işlemden devam etmeden önce dosyayı kapatarak hafızada kullandığı kaynakları serbest bırakmak isteyebilirsiniz. Yalnız buradaki kodların hata olmasa da çalıştırıldığına dikkat ediniz. Yani hata olsa da olmasa da her halükarda çalıştırılmasını istediğiniz kodları bu alanda kullanınız. Örneğin bir dosya ile çalışırken hata olsa da olmasa da her durumda kaynak kullanımını zorlamamak için dosyayı açık bırakmaz işleminiz bitince kapatırsınız.

Kendi İstisnalarınızı Oluşturmak

Bazı uygulamalarda duruma bağlı olarak kodunuz hata vermez ancak istediğiniz işlemi de gerçekleştirmez, çünkü yazdığınız kod ile uzlaşmayan bir kullanıcı davranışı ya da iş akışı durumu söz konusu olabilir. Bu durumda, kendi istisnanızı oluşturarak iş akışını ya da kullanıcı davranışını istediğiniz şekilde yönlendirmek isteyebilirsiniz.

Bunun için raise anahtar kelimesi ile Exception() fonksiyonunu birlilkte kullanarak kendi istisnanızı oluşturabilirsiniz.

Örneğin uygulamanızda kullanıcnızın belirli standartlarda veri girişi yapmasını isteyebilirsiniz ama doğru kullanım yapacağından emin olamazsınız. Bazı muzip(!) kullanıcılar koyduğunuz standartlara uymadıklarında neler olacağını merak edebilrler.

Örnek olarak bir sayı tahmini oyunu kodladığınızı ve uygulamanızın kullanıcının aklından tuttuğu sayıyı tahmin etmeye çalıştığını varsayalım. Eğer kullanıcıya bir sınır koymazsanız sonsuz seçeneği mevcut olduğu için uygulamanızın tuttuğu sayıyı tahmin etmesi çok çok uzun zaman alabilir. Diyelim ki, kullanıcıdan 0 ile 100 arasında bir sayı tutmasını istiyoruz;

aranan=int(input("Lütfen 0 ile 100 arasında bir sayı giriniz: "))
if aranan not in range(0,101):
    raise Exception("0 ile 100 arasında bir sayı girmenizi rica etmiştik!")

Çıktı:

Lütfen 0 ile 100 arasında bir sayı giriniz: 101
Traceback (most recent call last):
File "main.py", line 3, in <module>
raise Exception("0 ile 100 arasında bir sayı girmenizi rica etmiştik!")
Exception: 0 ile 100 arasında bir sayı girmenizi rica etmiştik!