try... except... as...
(Bu yazı önceki yazının devamı niteliğindedir)
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:
Burada ‘ValueError’ hata türünün adı, ‘invalid literal for int() with base 10: ‘f’’ ise hatanın açıklamasıdır. Eğer istersek, yazdığımız programda bu hata açıklamasına erişebiliriz. Dikkatlice bakın:
Bu programı çalıştırıp sayı değerli olmayan bir veri girersek hata çıktısı şöyle olacaktır:
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:
Bu arada, biraz önce yaptığımız gibi, hata türlerini grupladığınızda da bu yöntemi kullanabilirsiniz:
Burada
except falancaHata as filanca yapısını kullanarak falancaHata‘yı filanca olarak isimlendiriyor ve daha sonra bu ismi istediğimiz gibi kullanabiliyoruz. Böylece bütün hata türleri için hem kendi yazdığınız mesajı görüntüleyebiliyor, hem de özgün hata mesajını da çıktıya eklediğimiz için, kullanıcıya hata hakkında en azından bir fikir sahibi olma imkanı vermiş oluyoruz.try... except... else...
Daha önce de dediğimiz gibi, 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. Bu bölümde kısaca bu blokların ne işe yaradığından söz edeceğiz.
Öncelikle
try... except... else... bloğunun ne işe yaradığına bakalım. Esasında biz bu else deyimini daha önce de ‘koşullu ifadeler’ konusunu işlerken görmüştük. Buradaki kullanımı da zaten hemen hemen aynıdır. Diyelim ki elimizde şöyle bir şey var:
Burada eğer kullanıcı sayı yerine harf girerse
ValueError hatası alırız. Bu hatayı except ValueError: ifadesiyle yakalıyoruz ve hata verildiğinde kullanıcıya bir mesaj göstererek programımızın çökmesini engelliyoruz. 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. Dolayısıyla bu hatayı da yakalamak için şöyle bir şey yazabiliriz:
Bu şekilde hem
ValueError hatasını hem de ZeroDivisionError hatasını yakalamış oluruz. Bu kodların özelliği, except... bloklarının tek bir try... bloğunu temel almasıdır. Yani 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:
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. Bu şekilde bir kodlamanın bize getireceği avantaj, hatalar üzerinde belli bir kontrol sağlamamıza yardımcı olmasıdır. Yukarıdaki kodlar sayesinde hatalara bir nevi ‘teker teker gelin!’ mesajı vermiş oluyoruz. Böylelikle her blok içinde sadece almayı beklediğimiz hatayı karşılıyoruz. Mesela yukarıda ilk try... bloğu içindeki dönüştürme işlemi yalnızca ValueError hatası verebilir. else: bloğundan sonraki try... bloğunda yer alan işlem ise ancak ZeroDivisionError verecektir. Biz yukarıda kullandığımız yapı sayesinde her bir hatayı tek tek ve yeri geldiğinde karşılıyoruz. Bu durumun aksine, bölümün ilk başında verdiğimiz try... except bloğunda hem ValueError hem de ZeroDivisionError hatalarının gerçekleşme ihtimali bulunuyor. Dolayısıyla biz orada bütün hataları tek bir try... bloğu içine sıkıştırmış oluyoruz. İşte else: bloğu bu sıkışıklığı gidermiş oluyor. Ancak sizi bir konuda uyarmak isterim: Bu yapı, her akla geldiğinde kullanılacak bir yapı değildir. Büyük programlarda bu tarz bir kullanım kodlarınızın darmadağın olmasına, kodlarınız üzerindeki denetimi tamamen kaybetmenize de yol açabilir. Sonunda da elinizde bölük pörçük bir kod yığını kalabilir. Zaten açıkça söylemek gerekirse try... except... else... yapısının çok geniş bir kullanım alanı yoktur. Bu yapı ancak çok nadir durumlarda kullanılmayı gerektirebilir. Dolayısıyla bu üçlü yapıyı hiç kullanmadan bir ömrü rahatlıkla geçirebilirsiniz.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: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.finally... bloğu özellikle dosya işlemlerinde işimize yarayabilir. Henüz Python’da dosyalarla nasıl çalışacağımızı öğrenmedik, ama ben şimdilik size en azından dosyalarla çalışma prensibi hakkında bir şeyler söyleyeyim.
Genel olarak Python’da dosyalarla çalışabilmek için öncelikle bilgisayarda bulunan bir dosyayı okuma veya yazma kipinde açarız. Dosyayı açtıktan sonra bu dosyayla ihtiyacımız olan birtakım işlemler gerçekleştiririz. Dosyayla işimiz bittikten sonra ise dosyamızı mutlaka kapatmamız gerekir. Ancak eğer dosya üzerinde işlem yapılırken bir hata ile karşılaşılırsa dosyamızı kapatma işlemini gerçekleştirdiğimiz bölüme hiç ulaşılamayabilir. İşte
finally... bloğu böyle bir durumda işimize yarayacaktır:
Burada
finally... bloğu içine yazdığımız dosya.close() ifadesi dosyamızı güvenli bir şekilde kapatmaya yarıyor. Bu blok, yazdığımız program hata verse de vermese de işletilecektir.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:
Burada eğer kullanıcı 23 sayısını girerse, kullanıcıya bir hata mesajı gösterilip programdan çıkılacaktır. 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 NameError, TypeError, ZeroDivisionError, IOError, vb...
Bir örnek verelim:
Bu kodlar çalıştırıldığında, eğer kullanıcı, içinde Türkçe karakter geçen bir parola yazarsa kendisine
TypeError tipinde bir hata mesajı gösteriyoruz. Eğer kullanıcının parolası Türkçe karakter içermiyorsa hiçbir şey yapmadan geçiyoruz ve bir sonraki satırda kendisine ‘Parola kabul edildi!’ mesajını gösteriyoruz.raise deyimini, bir hata mesajına ek olarak bir işlem yapmak istediğimizde de kullanabiliriz. Örneğin:
Burada, eğer kullanıcı bir sayıyı 0‘a bölmeye çalışırsa, normal bir şekilde
ZeroDivisionError hatası verilecek ve programdan çıkılacaktır. Ama bu hata mesajıyla birlikte kullanıcıya ‘bir sayıyı 0’a bölemezsiniz,’ uyarısını da gösterme imkanını elde edeceğiz. Yani 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...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:
Yukarıdaki kod yardımıyla sadece
ZeroDivisionError adlı hatayı yakalayabiliriz. Eğer yazdığımız program başka bir hata daha veriyorsa, o hata mesajı yukarıdaki blokların kapsamı dışında kalacaktır. Ama eğer istersek yukarıdaki kodu şu şekilde yazarak olası bütün hataları yakalayabiliriz:
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.
Bu yöntem gözünüze çok pratik görünmüş olabilir, ama aslında hiç de öyle sayılmaz. Hatta oldukça kötü bir yöntem olduğunu söyleyebiliriz bunun. Çünkü bu tarz bir kod yazımının bazı dezavantajları vardır. Örneğin bu şekilde bütün hata mesajlarını aynı kefeye koyarsak, programımızda ne tür bir hata oluşursa oluşsun, kullanıcıya hep aynı mesajı göstermek zorunda kalacağız. Bu da, herhangi bir hata durumunda kullanıcıyı ne yapması gerektiği konusunda doğru düzgün bilgilendiremeyeceğimiz anlamına geliyor. Yani kullanıcı bir hataya sebep olduğunda tersliğin nereden kaynaklandığını tam olarak kestiremeyecektir.
Ayrıca, eğer kendimiz bir program geliştirirken sürekli olarak bu tarz bir yazımı benimsersek, kendi kodlarımızdaki hataları da maskelemiş oluruz. 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:
Burada olası bütün hata türlerini yakaladıktan sonra, bunların dışında bizim o anda öngöremediğimiz bir hatanın oluşması ihtimaline karşı
except: kodunu kullanarak kullanıcıya genel bir hata mesajı göstermeyi tercih edebiliriz. Böylece beklenmeyen bir hata meydana gelmesi durumunda da programımız çökmek yerine çalışmaya devam edebilecektir.Örnek Uygulama
Hata yakalama konusunu bütün ayrıntılarıyla inceledik. Gelin şimdi isterseniz ufak bir örnek yapalım.
Hatırlarsanız bir kaç bölüm önce şöyle bir uygulama yazmıştık:
Bu programın ne iş yaptığını biliyorsunuz. Bu program yardımıyla, kullanıcılarımızın bilgisayarlarındaki Python sürümünü kontrol edip, programımızın kullanılan sürüme göre tepki vermesini sağlıyoruz.
Ancak burada çok ciddi bir problem var. Python’ın 2.7 öncesi sürümlerinde sys modülünün
version_info() metodu farklı çıktılar verir. Mesela Python’ın 2.7 öncesi sürümlerinde version_info() metodunun major, minor veya micro gibi nitelikleri bulunmaz. Bu nitelikler Python programlama diline 2.7 sürümüyle birlikte geldi. Dolayısıyla yukarıdaki programı Python’ın 2.7 öncesi sürümlerinden biriyle çalıştıran kullanıcılarınız istediğiniz çıktıyı alamayacak, Python bu kullanıcalara şuna benzer bir hata mesajı göstererek programın çökmesine sebep olacaktır:
Python’ın 2.7 öncesi sürümlerinin kurulu olduğu bilgisayarlarda da programınızın en azından çökmemesi ve makul bir çıktı verebilmesi için yukarıdaki kodlar şöyle yazabilirsiniz:
Gördüğünüz gibi,
AttributeError adlı hatayı vereceğini bildiğimiz kısmı bir try... except bloğu içine aldık. Eğer programımız AttributeError hatasını veriyorsa, programımızın çalıştırıldığı sistem Python’ın 2.7 sürümünden daha düşük bir sürümü kullanıyor demektir. O yüzden kullanıcıya _2x_metni‘ni gösteriyoruz.
Elbette yukarıdaki programı yazmanın çok daha düzgün yolları vardır. Ama biz hata yakalama yöntemlerinin buna benzer durumlarda da bir alternatif olarak kullanılabileceğini bilelim. Ayrıca, dediğimiz gibi,
try... except blokları yukarıdaki sorunun çözümü için en uygun araçlar olmasa da, bazı durumlarda hatayı önlemenin makul tek yoludur.
Kaynaklar:
Başer, M. "Python Programlama Dili." İstanbul: Pusula Yayınnları (2001).
https://belgeler.yazbel.com/python-istihza/