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özdizimi Hataları

Sözdizimi 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ı 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 yazılımlar 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: spam
>>> '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 dizge meydana gelen istisnanın yerleşik 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; ayrılmış anahtar kelimeler değil.

Satırın devamı istisna türüne bağlı ayrıntılardan 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 belgelerden yararlanılabilir.

8.3. İstisnaların Ele Alınması

Belirli istisnaları ele alan yazılımlar 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 desteklediği başka bir kombinasyon) ile kullanıcı yazılımdan çıkabilir. Kullanıcı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:

  • Önce try bloğu (try ve except arasındaki ifade(ler)) işletilir.

  • Hiçbir istisna oluşmaz ise except bloğu atlanır ve try deyimin icrası son bulur.

  • Eğer try bloğu içinde bir istisna oluşur ise bloğun geri kalanı atlanır. İstisnanın türü except anahtar kelimesinden sonra kullanılan ile aynı ise try bloğunun kalan kısmı atlanır ve except bloğu yürütülür. Programın akışı try ... except kısmından sonra gelen ilk satırdan devam eder.

  • Adı except bloğunda geçmeyen bir istisna oluşur ise üst seviyedeki try ifadelerine geçirilir; ancak bunu ele alan bir şey bulunmaz ise bu bir ele alınmamış istisna olur ve yürütme işlemi yukarıda da görüldüğü gibi bir hata mesajı ile son bulur.

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 yazılımlama 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 bağımsız değişkeni olarak bilinen bir değer de bulunabilir. Bağımsız değişkennin varlığı ve türü istisnanın türüne bağlıdır. Bağımsız değişkeni olan istisna türleri için except bloğunda istisna adından (ya da listesinden) sonra bağımsız değişken değerini alacak bir değişken belirtilebilir:

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

Bir istisnanın bağımsız değişkeni var ise ele alınmayan istisna mesajının son kısmında (`ayrıntı') 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) işlevlerdeki istisnaları da ele alır. Ö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 deyimi yazılımcı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 bağımsız değişken oluşturulacak istisnanın adıdır ve ikinci bağımsız değişken ise istisnanın bağımsız değişkenidir.

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 mkan veren daha basit bir biçimi 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ı isimlendirebilir. İstisnalar genellikle, doğrudan veya dolaylı olarak, Exception sınıfından türetilir. Ö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 sunar. 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ı işlevlerde meydana gelen hataları rapor etmek için kendi istisnalarını tanımlar.

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

8.6. Son İşlemlerin 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.