Ders 12 / 16
İstisnalar
İstisna bir sınıfın örneğidir ve yakalama sınıf soyağacına bakar; on bir olay ile yedi yakalama sınıfı 77 çiftin 28'inde tutar, Exception on olayı yakalayıp üç program kusurunu yutar, BaseException on biri de yakalayıp çıkış isteğini de yutar.
İçindekiler
Buraya kadar ölçülen her protokol beklenen yolu izledi. for yineleyiciyi istedi ve aldı,
len bir sayı döndürdü, sorted sıralanmış bir liste verdi. Sözdizim çağırdı, özel yöntem
karşılık verdi, değer geri döndü. Peki bir protokol başarısız olursa ne olur?
Python’un yanıtı, önceki dersin döngü ölçümünde çoktan görüldü: üç öğelik nesnede dördüncü
__next__ çağrısı bir değer döndürmez, StopIteration fırlatır. Döngüyü bitiren şey bir
dönüş değeri değil, bir istisnadır. Bu ders o düzeneğin kendisine bakar. İstisna kavramı
ve hata yönetimi Programlama Temelleri kursunda kuruldu; orada kavram kuruldu, burada
Python’un onu hangi düzenekle gerçekleştirdiği ölçülüyor: istisna bir nesnedir, nesnenin bir
sınıfı vardır ve yakalama o sınıfın soyağacına bakar.
İstisna Bir Sınıfın Örneğidir
Fırlatılan şey bir ileti değil, bir nesnedir. raise deyimi bir nesne alır, except
deyimi bir sınıf alır ve ikisi isinstance ile karşılaştırılır. Bu tek cümle dersin
geri kalanını belirler: except bir sınıf adı yazdığı için, o sınıfın bütün alt
sınıfları da yakalanır.
Soyağacı iki uçtan başlar. Her istisna sınıfı BaseException’dan türer. Onun hemen altında
Exception durur ve program içinde ele alınması beklenen her şey oradan iner. Exception’ın
altında ara sınıflar vardır — ArithmeticError, LookupError, OSError — ve yaprakları
ZeroDivisionError, KeyError, FileNotFoundError gibi somut sınıflardır.
Bu ara sınıflar boş süs değildir. Bir yaprağı yakalamak yalnız onu yakalar; ara sınıfı yakalamak altındaki bütün yaprakları yakalar. Kaç tanesini yakaladığı ölçülebilir bir sayıdır ve bu dersin ölçtüğü sayı odur.
Ölçümün Kurgusu
Ölçüm on bir olay kurar: her biri gerçekten bir istisna fırlatan küçük bir işlev. Karşısına yedi yakalama sınıfı konur — iki yaprak, üç ara sınıf ve iki kök. Her olay her yakalama sınıfıyla denenir; sonuç 77 çiftlik bir tablodur.
Olaylar üç etikete ayrılır ve etiket kâhindir: kurguyu biz kurduğumuz için hangi olayın
gerçekten bir alan hatası, hangisinin bir program kusuru, hangisinin bir kesinti
olduğunu biliriz. Alan hatası ele alınması beklenen şeydir — eksik anahtar, çevrilemeyen
dizgi, bulunamayan dosya. Program kusuru kodun kendisindeki yanlıştır ve ele alınması değil,
dışarı çıkması gerekir. Kesinti programın bitirilmesi isteğidir ve yutulmamalıdır.
StopIteration dördüncü bir etikettir: hata değil, protokolün parçasıdır.
Ölçümün varsayımları:
- HD1 — On bir olayın hepsi gerçekten çalıştırılır ve fırlattıkları nesne yakalanıp saklanır; hiçbir sınıf adı elle yazılmaz, tablodaki her sınıf adı fırlatılan nesneden okunur.
- HD2 — Etiket kâhindir ve kurgunun kendisinden gelir; iletiye ya da sınıf adına bakarak çıkarılmaz.
- HD3 — Bir çiftin “tuttuğu”, yakalama sınıfının olayın nesnesini
isinstanceile kapsaması demektir;exceptdeyiminin yaptığı sınama budur. - HD4 — Soyağacı zinciri sınıfın çözüm sırasından okunur;
objectsayılmaz, çünkü her sınıf ondan iner ve ayırt edici değildir. - HD5 — Ölçüm bir başarım ölçümü değildir; sayılan şey yakalanan olay adedidir, süre değil.
Ölçüm — Yakalama Matrisi
"""Istisna hiyerarsisi: hangi sinifi yakalamak hangi olaylari da yakalar.""" import math KAYIT = [] class Izci: """Ortak tanimin cekirdegi; burada yalniz yineleme parcasi kullaniliyor.""" def __init__(self, ogeler=(1, 2, 3)): self.ogeler = list(ogeler) def __iter__(self): KAYIT.append("__iter__") self._i = 0 return self def __next__(self): KAYIT.append("__next__") if self._i >= len(self.ogeler): raise StopIteration deger = self.ogeler[self._i] self._i += 1 return deger def sifira_bolme(): return 1 // 0 def tasan_us(): return math.exp(10000) def eksik_anahtar(): return {"kuzey": 1}["yamac"] def tasan_dizin(): return [1, 2, 3][9] def cevrilemeyen_dizgi(): return int("kuzey") def bulunamayan_dosya(): return open("yok/olcum.txt") def biten_yineleyici(): return next(iter(Izci(()))) def yanlis_tip(): return len(5) def olmayan_ad(): return bilinmeyen_ad def olmayan_oznitelik(): return (1).kuzey def cikis_istegi(): raise SystemExit OLAYLAR = ( ("sıfıra bölme", sifira_bolme, "alan"), ("taşan üs", tasan_us, "alan"), ("eksik anahtar", eksik_anahtar, "alan"), ("taşan dizin", tasan_dizin, "alan"), ("çevrilemeyen dizgi", cevrilemeyen_dizgi, "alan"), ("bulunamayan dosya", bulunamayan_dosya, "alan"), ("biten yineleyici", biten_yineleyici, "protokol"), ("yanlış tip", yanlis_tip, "kusur"), ("olmayan ad", olmayan_ad, "kusur"), ("olmayan öznitelik", olmayan_oznitelik, "kusur"), ("çıkış isteği", cikis_istegi, "kesinti"), ) YAKALAMALAR = ( ("ZeroDivisionError", ZeroDivisionError), ("ArithmeticError", ArithmeticError), ("LookupError", LookupError), ("ValueError", ValueError), ("OSError", OSError), ("Exception", Exception), ("BaseException", BaseException), ) def firlat(islev): try: islev() except BaseException as e: return e return None print("olay sınıf hiyerarşideki yeri") for ad, islev, _ in OLAYLAR: s = type(firlat(islev)) zincir = " < ".join(k.__name__ for k in s.__mro__ if k is not object) print(f" {ad:19s} {s.__name__:18s} {zincir}") print() print("yakalama sınıfı yakalar alan protokol kusur kesinti kaçar") for yad, ysinif in YAKALAMALAR: sayac = {"alan": 0, "protokol": 0, "kusur": 0, "kesinti": 0} for ad, islev, tur in OLAYLAR: if isinstance(firlat(islev), ysinif): sayac[tur] += 1 y = sum(sayac.values()) print(f" {yad:18s} {y:7d} {sayac['alan']:5d} {sayac['protokol']:9d}" f" {sayac['kusur']:6d} {sayac['kesinti']:8d} {len(OLAYLAR) - y:6d}") print() tutan = sum(isinstance(firlat(i), ys) for _, i, _ in OLAYLAR for _, ys in YAKALAMALAR) print(f"olay {len(OLAYLAR)} | yakalama sınıfı {len(YAKALAMALAR)} | " f"çift {len(OLAYLAR) * len(YAKALAMALAR)} | tutan çift {tutan}") for etiket in ("kusur", "kesinti"): liste = [ad for ad, _, t in OLAYLAR if t == etiket] print(f"{etiket} {len(liste)}: {', '.join(liste)}") KAYIT.clear() firlat(biten_yineleyici) print(f"boş Izci: {KAYIT.count('__iter__')} kez __iter__, " f"{KAYIT.count('__next__')} kez __next__ — o tek çağrı değer " f"döndürmedi, StopIteration fırlattı")
olay sınıf hiyerarşideki yeri sıfıra bölme ZeroDivisionError ZeroDivisionError < ArithmeticError < Exception < BaseException taşan üs OverflowError OverflowError < ArithmeticError < Exception < BaseException eksik anahtar KeyError KeyError < LookupError < Exception < BaseException taşan dizin IndexError IndexError < LookupError < Exception < BaseException çevrilemeyen dizgi ValueError ValueError < Exception < BaseException bulunamayan dosya FileNotFoundError FileNotFoundError < OSError < Exception < BaseException biten yineleyici StopIteration StopIteration < Exception < BaseException yanlış tip TypeError TypeError < Exception < BaseException olmayan ad NameError NameError < Exception < BaseException olmayan öznitelik AttributeError AttributeError < Exception < BaseException çıkış isteği SystemExit SystemExit < BaseException yakalama sınıfı yakalar alan protokol kusur kesinti kaçar ZeroDivisionError 1 1 0 0 0 10 ArithmeticError 2 2 0 0 0 9 LookupError 2 2 0 0 0 9 ValueError 1 1 0 0 0 10 OSError 1 1 0 0 0 10 Exception 10 6 1 3 0 1 BaseException 11 6 1 3 1 0 olay 11 | yakalama sınıfı 7 | çift 77 | tutan çift 28 kusur 3: yanlış tip, olmayan ad, olmayan öznitelik kesinti 1: çıkış isteği boş Izci: 1 kez __iter__, 1 kez __next__ — o tek çağrı değer döndürmedi, StopIteration fırlattı
Sayıların Okunması
Üst tablo soyağacını veriyor ve derinliğin sabit olmadığını gösteriyor. ValueError iki
adımda köke varıyor, FileNotFoundError üç adımda. Aradaki fark bir ayrıntı değil: her ara
adım, o adımı yakalayan bir except deyimine karşılık gelir.
Alt tablo bunun bedelini sayıyor. ZeroDivisionError yakalaması 1 olayı tutuyor ve
10’unu kaçırıyor. Bir adım yukarıdaki ArithmeticError 2 tutuyor — sıfıra bölmeyi ve
taşan üssü. LookupError de 2 tutuyor, eksik anahtarı ve taşan dizini. Tek bir except
satırı yazıp iki ayrı olayı ele almanın yolu, o iki olayın ortak atasını yazmaktır.
OSError satırı 1 gösteriyor, çünkü ölçümde ondan inen tek olay var. Sayı kurgunun
sayısıdır, sınıfın kapsamının değil: OSError altında çok sayıda yaprak bulunur ve hepsi bu
satıra düşerdi. Ders kendi ölçtüğü sayıyı yazar.
Geniş Yakalamanın Yuttuğu
İki kök satırı tablonun asıl bulgusudur. Exception yakalaması 10 olayı tutuyor ve
yalnız 1’ini kaçırıyor. Bu on olayın 6’sı alan hatası — ele alınması beklenen şey.
Ama aynı satır 3 program kusurunu da yutuyor: yanlış tip, olmayan ad, olmayan öznitelik.
Bu üçü kullanıcı girdisinden ya da dosya sisteminden gelmez; kodun kendisindeki
yanlıştan gelir. except Exception yazan bir blok bunları da sessizce yakalar ve program
yanlış yazılmış hâliyle çalışmaya devam eder.
Bir de 1 protokol olayı yutuyor: StopIteration. Son satır bunun nereden geldiğini
söylüyor: boş Izci üzerinde 1 kez __iter__, 1 kez __next__ çağrıldı ve o tek
çağrı bir değer döndürmek yerine StopIteration fırlattı — öğe sayısından bir fazla. Geniş
bir yakalama, bir döngünün normal bitişini bir hata sanıp kendi dalına düşebilir.
BaseException bir adım daha ileri gidiyor: 11 olayın hepsini tutuyor, kaçan 0.
Fazladan tuttuğu tek şey çıkış isteğidir ve bu, dersin en keskin sayısıdır. Exception
onu kaçırır — kaçırması tasarımdır. Programın bitirilmesi isteği bir alan hatası değildir
ve ele alınacak bir şey değildir; except BaseException yazmak, o isteği de yutmak demektir.
İki kök satırı arasındaki tek fark budur ve tabloda 11 − 10 = 1 olarak durur.
Örüntü şudur: yakalama sınıfı yukarı çıktıkça tuttuğu olay artar, ama tuttuklarının yalnız bir bölümü ele alınması gereken şeydir. 77 çiftin 28’i tutuyor; bu 28 tutmanın 21’i iki kök satırından geliyor. Daraltmak, tutulan olayı azaltıp yutulanı sıfırlamaktır.
Yeniden Fırlatma ve Neden Zinciri
Bir istisnayı yakalamak onu ele almak zorunda değildir. Yakalayıp kayıt düşmek, sonra yeniden fırlatmak yaygın bir gerekliliktir ve Python bunun için birkaç ayrı biçim sunar. Biçimler arasındaki fark, dışarı çıkan nesnenin kimliği ve iç nesnenin durup durmadığıdır.
Her istisna nesnesi iki alan taşır. __context__, o istisna başka bir istisna ele alınırken
oluştuysa öbürüne işaret eder ve yorumlayıcı bunu kendiliğinden kurar. __cause__ ise
yalnız from yazıldığında dolar ve “bu istisnanın nedeni şudur” bildirimidir. from None
bağlamı bastırır.
- HD6 — Beş yeniden fırlatma biçimi aynı kaynaktan üretilir; kaynak her çağrıda yeni bir nesne ürettiği için biçimler birbirinin nesnesini paylaşmaz.
- HD7 — Nesnelerin aynı olup olmadığı
isile sınanır; kimlik sayısı basılmaz. - HD8 — Zincir uzunluğu, bastırılmamış bağlam ve neden bağları izlenerek sayılır; yutan biçimde dışarı istisna çıkmadığı için uzunluk sıfırdır.
- HD9 — Dal tablosu üç durumu ayrı ayrı çalıştırır ve her dalın çalışıp çalışmadığı, dalın içine konan bir kayıttan okunur; sıra tahmin edilmez, ölçülür.
"""Yeniden firlatma ve zincir: disari cikan nesne hangisi, ic nesne duruyor mu.""" ADIM = [] def kaynak(): """Alan hatasini uretir; her cagrida yeni bir nesne olusur.""" return {"kuzey": 1}["yamac"] def ciplak(): try: kaynak() except KeyError: raise def sarmala(): try: kaynak() except KeyError: raise RuntimeError("ölçüm okunamadı") def nedeniyle(): try: kaynak() except KeyError as e: raise RuntimeError("ölçüm okunamadı") from e def bastir(): try: kaynak() except KeyError: raise RuntimeError("ölçüm okunamadı") from None def yut(): try: kaynak() except KeyError: return None BICIMLER = (("çıplak raise", ciplak), ("sarmalayan", sarmala), ("from ile", nedeniyle), ("from None ile", bastir), ("yutan", yut)) print(f"{'biçim':<17s}{'dışarı çıkan':<15s}{'neden':<14s}" f"{'bağlam':<14s}zincir") for ad, islev in BICIMLER: try: islev() except BaseException as d: cikan = type(d).__name__ neden = type(d.__cause__).__name__ if d.__cause__ else "-" baglam = type(d.__context__).__name__ if d.__context__ else "-" n, k = 0, d while k is not None: n += 1 k = k.__cause__ or (None if k.__suppress_context__ else k.__context__) else: cikan = neden = baglam = "-" n = 0 print(f" {ad:<15s}{cikan:<15s}{neden:<14s}{baglam:<14s}{n}") print() try: try: kaynak() except KeyError as e: icteki = e raise except KeyError as d: print("çıplak raise dışarı aynı nesneyi mi çıkardı:", d is icteki) try: try: kaynak() except KeyError as e: icteki = e raise RuntimeError("ölçüm okunamadı") from e except RuntimeError as d: print("from ile çıkan nesne aynı mı:", d is icteki) print("from ile nedeni aynı nesne mi:", d.__cause__ is icteki) print() DALLAR = ("try", "except", "else", "finally") print(f"{'durum':<16s}" + "".join(f"{d:<9s}" for d in DALLAR)) for durum, islev in (("hatasız", lambda: 1), ("yakalanan", kaynak), ("yakalanmayan", lambda: 1 // 0)): ADIM.clear() try: try: ADIM.append("try") islev() except KeyError: ADIM.append("except") else: ADIM.append("else") finally: ADIM.append("finally") except ZeroDivisionError: pass print(f" {durum:<14s}" + "".join(f"{('çalıştı' if d in ADIM else 'atlandı'):<9s}" for d in DALLAR))
biçim dışarı çıkan neden bağlam zincir çıplak raise KeyError - - 1 sarmalayan RuntimeError - KeyError 2 from ile RuntimeError KeyError KeyError 2 from None ile RuntimeError - KeyError 1 yutan - - - 0 çıplak raise dışarı aynı nesneyi mi çıkardı: True from ile çıkan nesne aynı mı: False from ile nedeni aynı nesne mi: True durum try except else finally hatasız çalıştı atlandı çalıştı çalıştı yakalanan çalıştı çalıştı atlandı çalıştı yakalanmayan çalıştı atlandı atlandı çalıştı
Zincirin Ödettiği
Çıplak raise dışarı KeyError çıkarıyor ve alt satır bunun aynı nesne olduğunu
söylüyor. Yeni bir nesne üretilmediği için zincir 1. Yakalayıp yeniden fırlatmak bilgi
kaybettirmez.
Sarmalayan biçim dışarı RuntimeError çıkarıyor; __cause__ boş ama __context__
KeyError’ı tutuyor. Zincir 2. Bu bağı kimse yazmadı — istisna başka bir istisna ele
alınırken oluştuğu için yorumlayıcı kendiliğinden kurdu.
from ile yazılan biçimde __cause__ doluyor ve zincir yine 2. Fark çıktının alt
satırlarında: dışarı çıkan nesne iç nesneyle aynı değil, ama __cause__ aynı
nesneye işaret ediyor. Sarmalamakla from yazmak aynı zincir uzunluğunu verir; ayrıldıkları
yer, bağın bildirilmiş mi yoksa kendiliğinden mi kurulduğudur.
from None zinciri 1’e indiriyor. __context__ hâlâ dolu, ama bastırıldığı için
sayıma girmiyor — dışarıdan bakan biri KeyError’ı göremez. Alan hatasını gizlemek isteyen
bir sınır için doğru araç, iç ayrıntının kaybolduğunu bilerek kullanılan bir araçtır.
Yutan biçim 0 veriyor: dışarı hiçbir şey çıkmıyor. Bir önceki bölümün geniş yakalamasıyla birleştiğinde bu sıfır tehlikelidir — üç program kusurunun yutulduğu yer tam olarak burasıdır.
Son tablo dört dalın hangi durumda çalıştığını veriyor. try üç durumda da çalışıyor;
finally de üç durumda da çalışıyor — yakalanmayan istisna dışarı çıkarken bile. else
yalnız istisna oluşmadığında çalışıyor, except yalnız yakalandığında. Kaynağı
try içinde tutup başarı yolunu else’e taşımak, except deyiminin yanlışlıkla fazla iş
yakalamasını engeller.
Özet
- İstisna bir nesnedir, nesnenin bir sınıfı vardır ve
exceptbir sınıf alır; yakalama o sınıfın bütün alt sınıflarını kapsar. - On bir olay ile yedi yakalama sınıfı 77 çift üretir ve bunların 28’i tutar; yaprak
sınıflar 1–2 olay tutarken
Exception10,BaseException11 tutar. Exceptionyakalaması 6 alan hatasının yanında 3 program kusurunu ve 1 protokol olayını da yutar;BaseExceptionfazladan çıkış isteğini yutar.- Çıplak
raiseaynı nesneyi dışarı çıkarır (zincir 1); sarmalamak vefromyazmak zinciri 2 yapar,from Noneonu 1’e indirir, yutmak 0’a. finallyüç durumda da çalışır,elseyalnız istisna oluşmadığında; başarı yolunuelseiçine almakexceptdeyiminin kapsamını daraltır.
Sonraki Adım
Bu dersin bütün sınıfları hazır gelenlerdi: KeyError, ValueError, OSError. Ölçüm
onları ortak atalarına göre gruplayabildi, çünkü soyağacı önceden kurulmuştu. Peki fırlatılan
şey alanın kendi hatasıysa — ölçüm aygıtının kapsam dışı değer bildirmesi, kayıtta bir
alanın eksik olması gibi? O zaman sınıfı yazan biz oluruz ve onu soyağacının neresine
astığımız, sonraki yakalamaların hangisinin tutacağını belirler. Sonraki ders özel istisna
sınıflarının hiyerarşideki yerini ve o yerin yakalama davranışına ödettiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.