Hata yakalama: try-except

Hata yakalama (exception handling) beklenmedik durumlarda programınızın bir hata mesajı vermesi ve çalışmayı durdurması yerine, hataya kendi istediğimiz şekilde cevap vermesini sağlamanın bir yoludur. Hata yakalama Python programcılığının önemli bir parçasıdır, kaynak kodunun çok karışık hale getirmeden programınızın güvenilir bir şekilde çalışmasını sağlar.

Bir örnekle başlayalım: Etkileşimli çalışarak kullanıcıdan sayılar alan ve aldığı sayıların karesini ekrana basan bir program yazalım. Boş satır okuduğunda program sonlansın.

while True:
    x = raw_input()
    if not x:
        break
    print float(x)**2

Programı çalıştırdığımızda çeşitli girdilerle verdiği cevaplar şöyle olabilir.

Bir sayı girin:6
36.0
Bir sayı girin:-4
16.0
Bir sayı girin:1.4
1.96
Bir sayı girin:abc

Traceback (most recent call last):
  File "/home/kaan/inout.py", line 7, in <module>
    print float(x)**2
ValueError: could not convert string to float: abc

Son girilen değer (abc) bir sayı olmadığı için, float fonksiyonu bir hata yayınladı, yorumlayıcı hatanın nerede olduğunu gösteren bir mesaj gösterdi ve programı durdurdu.

Hata mesajını engellemek için x‘in sayısal olmayan değerlerinin görmezden gelinmesini sağlamaya çalışalım. Meselâ x dizesinin isdigit metodunu kullanmayı düşünebiliriz. Bu metod, x sadece rakamlardan oluşuyorsa doğru, aksi takdirde yanlış verir.

while True:
    x = raw_input("Bir sayı girin: ")
    if not x:
        break
    if not x.isdigit():
        continue
    print float(x)**2

Bu program sayısal olmayan girdileri görmezden gelir, ama negatif sayıları ve virgüllü sayıları da görmezden gelir.

Bir sayı girin: 5
25.0
Bir sayı girin: abc
Bir sayı girin: -1
Bir sayı girin: 1.2
Bir sayı girin: ...

Ne yapmalıyız? Belki uğraşıp, içinde sadece rakamlar, en fazla bir nokta, ve en başta olmak üzere en fazla bir eksi işareti barındıran dizeleri seçen bir fonksiyon yazabiliriz (ama o zaman da 1.2e3 gibi sayıları dışlamış oluruz).

Daha iyi bir çözüm Python’un hata yakalama özelliğini kullanmaktır.

while True:
    x = raw_input("Bir sayı girin: ")
    if not x:
        break
    try:
        y = float(x)
    except ValueError:
        print "Geçersiz sayı"
        continue
    print float(x)**2

Bu programı meselâ şöyle çalıştırabiliriz ve tam istediğimizi yapar.

Bir sayı girin: 4
16.0
Bir sayı girin: abc
Geçersiz sayı
Bir sayı girin: -1.4
1.96

Hata yakalama sürecinde önce try ile başlayan blok çalıştırılır. Eğer bu blok içinde bir hata yayınlanırsa, ve except kısmının başlığında bu hatanın kodu verilmişse, yorumlayıcı hata mesajı yayınlamak yerine except blokunu çalıştırır. Bu yapı sayesinde hataları “zarifçe” yönetmek mümkün olur, meselâ programınız bir hata durumunda sonlanıvermez.

Hata kodları

except başlığında kullanacağımız hata isimlerini nereden bileceğiz? Yazdığınız programa tam bir kötümser gözüyle bakın ve ne yanlışlık olabileceğini düşünün. Programınızda hatalı olabilecek şeyleri önceden yorumlayıcıda deneyin (meselâ float fonksiyonunu “abc” ile çağırın) ve çıkan hata mesajından hata ismini okuyun. Kullandığınız fonksiyonların hangi durumda ne hata yayınlayacağı belgelerde verilmiştir. Aklınıza gelmeyecek hata durumlarını yardım belgelerinden okuyup hazırlıklı olabilirsiniz.

Python kütüphane belgelerinde tanımlı bütün hata durumlarının listesi mevcut. Birkaç yaygın hata durumuna örnek verelim.

Sözgelişi, varolmayan bir dosyayı açmaya çalıştığımızda IOError hatası alırız.

>>> f = open("hedehodo.txt")
Traceback (most recent call last):
 File "<pyshell#16>", line 1, in <module>
 f = open("hedehodo.txt")
 IOError: [Errno 2] No such file or directory: 'hedehodo.txt'

Bu hatayı yakalamak için:

dosyaadi = "hedehodo.txt"
try:
    f = open(dosyaadi)
except IOError:
    print dosyaadi, "dosyası açılamadı."

Sıralı bir nesnede, varolmayan bir indeks kullandığımızda IndexError alırız.

>>> L
[1, 2, 3]
>>> L[5]
Traceback (most recent call last):
 File "<pyshell#39>", line 1, in <module>
 L[5]
 IndexError: list index out of range

Programınızı Control-C tuş bileşimi ile durdurmaya çalıştığınızda KeyboardInterrupt hatası yayınlanır. Bu hatayı yakalayarak programınızın beklenmedik şekilde durmasını önleyebilirsiniz.

while True:
    try:
        x = raw_input("Naber? ")
        if x=="bitir":
            break
        print x
    except KeyboardInterrupt:
        print "Bitirmek için 'bitir' yazın"
        continue

Çalıştıralım ve üçüncü “Naber?”de Control-C’ye basalım:

Naber? iyi
iyi
Naber? iyi
iyi
Naber? 
Bitirmek için 'bitir' yazın
Naber? bitir

Programın sonlanmadan önce bitirmesi gereken işler varsa, KeyboardInterrupt hatasını yakalamak size bu işleri bitirme fırsatı da verecektir.

Daha fazla bilgi için Fırat Özgül’ün kitabı , Doug Hellmann’ın blogu ve Jeff Knupp’ın blogu yararlı olacaktır.

Sinyal olarak hata kodları

Hata kodları sadece bir sorun çıktığında kullanılmaz. Bazen normal bir duruma dair sinyal vermeye de yararlar. Sözgelişi

for x in [1,2,3]:
    print x

işleminde, döngü listenin sonuna geldiğini nasıl anlar? Bu kod, perde arkasında döngüdeki sıralı nesneye iter() fonksiyonunu uygular ve “iterable” bir nesne yaratır. Bu nesnenin next() metodu elemanlara sırayla erişilmesini sağlar. Nesnenin son elemanı verildikten sonra çağrılan next() bir StopIteration hatası yayınlar

>>> L = [1,2,3]
>>> li = iter(L)
>>> li.next()
1
>>> li.next()
2
>>> li.next()
3
>>> li.next()

Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    li.next()
StopIteration

Döngümüz perde arkasında StopIteration hatasını yakalayarak işlemi bitirir.

Hata yayınlamak

Kendi yazdığınız fonksiyonlarda mevcut hata durumlarını yayınlamak için raise komutunu kullanabilirsiniz. Sözgelişi, negatif parametre aldığında ValueError yayınlayan bir faktöryel fonksiyonu yazalım.

def faktoryel(x):
    x = int(x)
    if x<0:
        raise ValueError("Negatif değer")
    p = 1
    i = 1
    while i<=x:
        p *= i
        i += 1
    return p

Komut satırında çalıştıralım (hata mesajlarını kısaltıyorum).

>>> faktoryel(7)
5040
>>> faktoryel(-3)
...
ValueError: Negatif değer
>>> faktoryel("abc")
...
ValueError: invalid literal for int() with base 10: 'abc'

Bu şekilde kendi yazdığınız programlara try/except yapısı ile yönetilebilecek hata kodları ekleyebilirsiniz.

Yeni hata durumları yaratmak

Standart hata durumları bir nesne hiyerarşisi içinde tanımlanırlar. Yeni bir hata durumu tanımlarken varolan bir hata sınıfını temel alabiliriz.

Örnek olarak, iki vektörün iç çarpımını veren bir fonksiyon yazalım. Vektörlerin boyu eşit değilse, yeni tanımlayacağımız bir VektorBoyuHatasi yayınlasın.

class VektorBoyuHatasi(Exception):
    pass

def ic_carpim(L1, L2):
    if len(L1)!=len(L2):
        raise VektorBoyuHatasi
    return sum( [a*b for (a,b) in zip(L1,L2)] )

Fonksiyonu komut satırında çağıralım.

>>> ic_carpim([1,2,3],[-1,4,1])
10
>>> ic_carpim([1,2,3],[-1,4,1,5])
...
VektorBoyuHatasi

Yukarıdaki hata tanımı en basit yaklaşımdır, ama iş görmeye yeter. Daha fazla ayrıntı (meselâ bir hata mesajı eklemek) için Python Tutorial‘a bakabilirsiniz.

Yorum bırakın