Altblümler

 
8. Hatalar ve İstisnalar

Şu ana kadar hata mesajlarından pek bahsedilmedi; ancak örnekleri denediyseniz muhtemelen birkaç tane görmüşsünüzdür. Birbirinden ayırt edilebilen en az iki tür hata mevcuttur: sözdizim hataları ve istisnalar.

 
8.1 Sözdizim Hataları

Sözdizim hataları ayrıştırma (parsing) hataları olarak da bilinirler ve Python öğrenirken en çok bunlar ile karşılaşırsınız:

>>> while 1 print 'Merhaba'
  File "<stdin>", line 1, in ?
    while 1 print 'Merhaba'
                ^
SyntaxError: invalid syntax

Ayrıştırıcı (parser) sorun olan satırı basar ve satır içinde hatanın algılandığı ilk noktayı küçük bir `ok' ile gösterir. Hata oktan önce gelen kısımdan kaynaklanmaktadır. Örnekte hata print anahtar kelimesinde fark edilmektedir; çünkü ondan önce bir iki nokta üst üste (":") karakteri eksiktir. Dosya adı ve satır numarası da yazdırılmaktadır ki yorumlayıcı girişinin bir dosyadan gelmesi durumunda hatanın nereden kaynaklandığını bilesiniz.

 
8.2 İstisnalar

Bir deyim ya da ifade sözdizimsel olarak doğru olsa da yürütülmek istendiğinde bir hataya sebep olabilir. İcra sırasında meydana gelen hatalara istisna denir. İstisnaları nasıl ele alabileceğinizi yakında öğreneceksiniz. Çoğu istisnalar programlar tarafından ele alınmaz ve aşağıdakiler gibi hata mesajları ile sonuçlanırlar:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: illegal argument type for built-in operation

Hata mesajının son satırı sorunun ne olduğunu belirtir. İstisnaların farklı türleri vardır ve istisnanın türü hata mesajının bir bölümü olarak yazdırılır. Örneklerdeki istisna türleri: ZeroDivisionError, NameError ve TypeError. İstisna türü olarak yazdırılan karakter dizisi meydana gelen istisnanın yerleşik (built-in) ismidir. Bu bütün yerleşik istisnalar için geçerlidir; ancak kullanıcı tanımlı istisnalar için böyle olmayabilir. Standart istisna isimleri yerleşik belirteçlerdir ( identifier); ayrılmış anahtar kelimeler değil.

Satırın devamı istisna türüne bağlı detaylardan oluşur ve anlamı istisna türüne bağlıdır.

Hata mesajının baş kısmında istisnanın meydana geldiği yer yığın dökümü şeklinde görülür. Bu genellikle istisnanın gerçekleştiği noktaya gelene kadar işletilen kaynak kodu şeklinde olur; ancak standart girdiden okunan satırlar gösterilmez.

Yerleşik istisnalar ve bunların anlamları için Python ile gelen dokümanlardan yararlanılabilir.

 
8.3 İstisnaların Ele Alınması

Belirli istisnaları ele alan programlar yazmak mümkündür. Aşağıdaki örnek kullanıcıdan geçerli bir tamsayı girilene kadar kullanıcıdan giriş yapması istenir. Control-C tuş kombinasyonu (ya da işletim sisteminin destekldiği başka bir kombinasyon) ile kullanıcı programdan çıkabilir. Kullanıcının sebep olduğu bu olay ise KeyboardInterrupt istisnasının oluşmasına neden olur.

>>> while True:
...     try:
...         x = int(raw_input("Lütfen bir rakam giriniz: "))
...         break
...     except ValueError:
...         print "Bu geçerli bir giriş değil.  Tekrar deneyin..."
...

try deyimi aşağıdaki gibi çalışır:

Bir try deyimi farklı istisnaları yakalayabilmek için birden fazla except bloğuna sahip olabilir. Bir except bloğu parantez içine alınmış bir liste ile birden fazla istisna adı belirtebilir. Örnek:

... except (RuntimeError, TypeError, NameError):
...     pass

Son except bloğu istisna adı belirtilmeden de kullanılıp herhangi bir istisnayı yakalayabilir. Bunu çok dikkatli kullanın, çünkü çok ciddi bir programlama hatasını bu şekilde gözden kaçırabilirsiniz! Bu özellik bir hata mesajı bastırıp ve tekrar bir istisna oluşturarak çağıranın istisnayı ele almasını da sağlamak için kullanılabilir:

import string, sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(string.strip(s))
except IOError, (errno, strerror):
    print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise

try ... except ifadesinin seçimlik else bloğu da vardır. Bu her except bloğunun ardına yazılır ve try bloğunun istisna oluşturmadığı durumlarda icra edilmesi gereken kod bulunduğu zaman kullanılır. Örnek:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()

else bloğu kullanmak try bloğuna ek satırlar eklemekten iyidir çünkü bu try ... except ifadesi tarafından korunan kodun oluşturmadığı bir istisnanın kazara yakalanmasını engeller.

Bir istisna meydana geldiğinde istisna argümanı olarak bilinen bir değer de bulunabilir. Argümanın varlığı ve tipi istisnanın tipine bağlıdır. Argümanı olan istisna tipleri için except bloğunda istisna adından (ya da listesinden) sonra argüman değerini alacak bir değişken belirtilebilir:

>>> try:
...     spam()
... except NameError, x:
...     print 'name', x, 'undefined'
...
name spam undefined

Bir istisnanın argümanı var ise ele alınmayan istisna mesajının son kısmında (`detay') basılır.

İstisna işleyiciler (exception handlers) sadece try bloğu içinde meydana gelen istisnaları değil try bloğundan çağırılan (dolaylı olarak bile olsa) fonksiyonlardaki istisnaları da ele alırlar. Örnek:

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError, detail:
...     print 'Handling run-time error:', detail
...
Handling run-time error: integer division or modulo

 
8.4 İstisna Oluşturma

raise anahtar kelimesi programcının kasıtlı olarak bir istisna oluşturmasını sağlar. Örnek:

>>> raise NameError, 'Merhaba'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: Merhaba

raise için ilk argüman oluşturulacak istisnanın adıdır ve ikinci argüman ise istisnanın argümanıdır.

Eğer bir istisnanın oluşup oluşmadığını öğrenmek istiyor; fakat bunu ele almak istemiyorsanız, raise ifadesinin istisnayı tekrar oluşturmanıza imkan veren daha basit bir formu var:

>>> try:
...     raise NameError, 'Merhaba'
... except NameError:
...     print 'Bir istisna gelip geçti!'
...     raise
...
Bir istisna gelip geçti!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: Merhaba

 
8.5 Kullanıcı Tanımlı İstisnalar

Programlar yeni bir istisna sınıfı yaratarak kendi istisnalarını isimlendirebilirler. İstisnalar genellikle, doğrudan veya dolaylı olarak, Exception sınıfından türetilirler. Örnek:

>>> class bizimHata(Exception):
...     def __init__(self, deger):
...         self.deger = deger
...     def __str__(self):
...         return `self.deger`
...
>>> try:
...     raise bizimHata(2*2)
... except bizimHata, e:
...     print 'İstisnamız oluştu, deger:', e.deger
...
İstisnamız oluştu, deger: 4
>>> raise bizimHata, 'aaah!'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.bizimHata: 'aaah!'

İstisna sınıfları diğer sınıfların yapabildiği her şeyi yapabilecek şekilde tanımlanabilirler, fakat genellikle basit tutulurlar ve sıklıkla sadece istisnayı işleyenlerin hata hakkında bilgi almasını sağlayacak birkaç özellik sunarlar. Birkaç farklı istisna oluşturabilen bir modül yaratırken, yaygın bir uygulama da bu modül tarafından tanımlanan istisnalar için bir temel sınıf yaratıp ve farklı hata durumları için bundan başka istisna sınıfları türetmektir:

class Error(Exception):
    """Bu modüldeki istisnalar için temel sınıf."""
    pass

class GirisHatasi(Error):
    """Giriş hataları için oluşacak istisna.

    Özellikler:
        ifade -- hatanın oluştuğu giriş ifadesi
        mesaj -- explanation of the error
    """

    def __init__(self, ifade, mesaj):
        self.ifade = ifade
        self.mesaj = mesaj

class GecisHatasi(Error):
    """İzin verilmeyen bir durum geçişine teşebbüs edildiğinde oluşacak istisna.

    Özellikler:
        onceki -- geçiş başlangıcındaki durum
        sonraki -- istenen yeni durum
        mesaj -- durum geçişine izin verilmemesinin sebebi
    """

    def __init__(self, onceki, sonraki, mesaj):
        self.onceki = onceki
        self.sonraki = sonraki
        self.mesaj = mesaj

Çoğu standart modül kendi tanımladıkları fonksiyonlarda meydana gelen hataları rapor etmek için kendi istisnalarını tanımlarlar.

Sınıflar üzerine daha fazla bilgi sonraki bölümde sunulacaktır.

 
8.6 Son işlemlerinin Belirlenmesi

try deyiminin her durumda yürütülecek işlemleri belirten seçimlik bir bloğu da vardır. Örnek:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Goodbye, world!'
...
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2
KeyboardInterrupt

finally bloğu try bloğu içinde bir istisna oluşsa da oluşmasa da yürütülür. Bir istisna oluşursa finally bloğu icra edildikten sonra istisna tekrar oluşturulur. Finally bloğu try deyimi break veya return ile sonlanırsa da icra edilir.

try deyiminin bir ya da daha fazla except bloğu veya bir finally bloğu olmalıdır; ancak her ikisi bir arada olamaz.