Ders 13 / 16
Özel İstisnalar
Özel bir istisna sınıfının hiyerarşideki yeri onu kimin yakalayacağını belirler; beş sınıf ile altı yakalama 30 çiftin 13'ünde tutar, alan kökü tek adla yedi hatayı ele alır, ödünç alınan taban yalnız üçünü tutar ve klozu geniş yazmak dar klozu sıfır kez çalıştırır.
İçindekiler
Önceki dersin bütün sınıfları hazır gelenlerdi. KeyError, ValueError, FileNotFoundError —
soyağaçları önceden kurulmuştu ve ölçüm onları ortak atalarına göre gruplayabildi. Bir ara
sınıfı yakalamanın altındaki bütün yaprakları yakaladığını gördük; yerleştirmeyi yapan başkasıydı.
Peki fırlatılan şey alanın kendi hatasıysa? Ölçüm aygıtının kabul aralığı dışında değer bildirmesi, bir kayıtta zorunlu alanın bulunmaması, sayıya çevrilemeyen bir ham veri. Bunlar için bir sınıf yazmak gerekir ve o sınıfı soyağacının neresine astığımız bir üslup tercihi değildir: yeri, onu kimin yakalayacağını belirler. Bu dersin sorusu budur.
Alanın Kendi Hatası Bir Tiptir
Bir hatayı ileti dizgisiyle anlatmak ile tiple anlatmak arasında ölçülebilir bir fark vardır.
İleti bir dizgidir; onu okuyan kod dizgiyi ayrıştırmak zorundadır ve ileti değişince ayrıştırma
bozulur. Tip ise except deyiminin doğrudan sınadığı şeydir ve sınama isinstance ile yapılır.
Özel istisna yazmak bir sınıf tanımından ibarettir. Sınıf, taşıdığı alan verisini öznitelik olarak tutabilir — hangi alan, hangi değer — ve yakalayan kod bunu ileti ayrıştırmadan okur.
Asıl karar tabandır. Alan hataları için bir kök sınıf tanımlanır ve somut hatalar ondan
iner; böylece tek bir except satırı hepsini kapsar. Bunun yanında somut sınıf, anlamına
uyan yerleşik bir sınıftan da inebilir: kapsam dışı değer bir ValueError’dır, eksik alan
bir KeyError’dır. İki tabanlı bu yerleştirme bir kolaylık gibi görünür ve bedeli ölçülür.
Kökün Seçilmesi
Ölçüm dört alan sınıfı kurar. OlcumHatasi köktür ve doğrudan Exception’dan iner.
KapsamDisiDeger kökten ve ValueError’dan, EksikAlan kökten ve KeyError’dan
iner. BozukKayit yalnız kökten iner. Beşinci sınıf AygitHatasi kökten inmez — ayrı bir
ağaçtır ve kökün kapsamadığını göstermek için vardır.
Yerleştirmenin kök tarafı bir ağaç olarak şöyle görünür; ödünç tabanlar ağacın öbür dallarında durdukları için satır sonuna not düşülmüştür:
# ogretilen dokum , calistirilmamistir Exception ├── OlcumHatasi alanın kökü │ ├── KapsamDisiDeger ayrıca ValueError │ ├── EksikAlan ayrıca KeyError │ └── BozukKayit yalnız kökten └── AygitHatasi kökten inmez
Ölçümün varsayımları:
- HD10 — Soyağacı zinciri sınıfın kendi çözüm sırasından okunur, elle yazılmaz;
objectsayılmaz çünkü her sınıf ondan iner. - HD11 — Bir çiftin “tuttuğu”, yakalama sınıfının fırlatılan sınıfı
issubclassile kapsaması demektir;exceptdeyiminin yaptığı sınama budur ve sınıf düzeyinde önceden bilinebilir. - HD12 — “Yabancı yakalama”, alandan habersiz bir kodun kendi işi için yazdığı
except ValueErrorya daexcept KeyErrorklozudur; alan sınıfının oraya düşmesi sızmadır. - HD13 — Sınıflar yalnız yerleştirme için tanımlanır; hiçbiri davranış eklemez, bu yüzden ölçülen tek şey hiyerarşideki yerdir.
"""Ozel istisna: sinifin hiyerarsideki yeri yakalama davranisini belirler.""" class OlcumHatasi(Exception): """Alanin koku: olcum kaydini isleyen her hata buradan iner.""" def __init__(self, alan, deger=None): super().__init__(alan) self.alan = alan self.deger = deger def __str__(self): return f"{type(self).__name__}(alan={self.alan!r}, deger={self.deger!r})" class KapsamDisiDeger(OlcumHatasi, ValueError): """Deger okundu ama alanin kabul araligi disinda.""" class EksikAlan(OlcumHatasi, KeyError): """Kayitta zorunlu alan yok.""" class BozukKayit(OlcumHatasi): """Kayit sayiya cevrilemedi; alanin disinda bir karsiligi yok.""" class AygitHatasi(Exception): """Alan kokunden inmez: ayri bir agac.""" SINIFLAR = (("OlcumHatasi", OlcumHatasi), ("KapsamDisiDeger", KapsamDisiDeger), ("EksikAlan", EksikAlan), ("BozukKayit", BozukKayit), ("AygitHatasi", AygitHatasi)) YAKALAMALAR = (("OlcumHatasi", OlcumHatasi), ("KapsamDisiDeger", KapsamDisiDeger), ("ValueError", ValueError), ("KeyError", KeyError), ("LookupError", LookupError), ("Exception", Exception)) print("sınıf hiyerarşideki yeri") for ad, s in SINIFLAR: zincir = " < ".join(k.__name__ for k in s.__mro__ if k is not object) print(f" {ad:16s}{zincir}") print() baslik = "".join(f"{y:<17s}" for y, _ in YAKALAMALAR) print(f"{'fırlatılan':<18s}{baslik}") for ad, s in SINIFLAR: hucre = "".join(f"{('tutar' if issubclass(s, ys) else '-'):<17s}" for _, ys in YAKALAMALAR) print(f" {ad:<16s}{hucre}") print() tutan = sum(issubclass(s, ys) for _, s in SINIFLAR for _, ys in YAKALAMALAR) print(f"sınıf {len(SINIFLAR)} | yakalama {len(YAKALAMALAR)} | " f"çift {len(SINIFLAR) * len(YAKALAMALAR)} | tutan çift {tutan}") kok = sum(issubclass(s, OlcumHatasi) for _, s in SINIFLAR) sizan = sum(issubclass(s, (ValueError, KeyError)) for _, s in SINIFLAR) print(f"alan kökünden inen {kok} | yabancı yakalamaya sızan {sizan}")
sınıf hiyerarşideki yeri OlcumHatasi OlcumHatasi < Exception < BaseException KapsamDisiDeger KapsamDisiDeger < OlcumHatasi < ValueError < Exception < BaseException EksikAlan EksikAlan < OlcumHatasi < KeyError < LookupError < Exception < BaseException BozukKayit BozukKayit < OlcumHatasi < Exception < BaseException AygitHatasi AygitHatasi < Exception < BaseException fırlatılan OlcumHatasi KapsamDisiDeger ValueError KeyError LookupError Exception OlcumHatasi tutar - - - - tutar KapsamDisiDeger tutar tutar tutar - - tutar EksikAlan tutar - - tutar tutar tutar BozukKayit tutar - - - - tutar AygitHatasi - - - - - tutar sınıf 5 | yakalama 6 | çift 30 | tutan çift 13 alan kökünden inen 4 | yabancı yakalamaya sızan 2
Ödünç Alınan Tabanın Ödettiği
Üst tablo yerleştirmenin sonucunu veriyor. OlcumHatasi sütunu 4 satırda tutuyor —
kökten inen dört sınıfın hepsi. AygitHatasi satırı boş; kök onu kapsamıyor, çünkü ondan
inmiyor. Alanın kökünü yazmak, alanın sınırını yazmaktır.
İlginç olan ValueError ve KeyError sütunlarıdır. Her biri 1 satırda tutuyor ve o
satırlar ödünç taban alan sınıflardır. EksikAlan üstelik LookupError sütununda da tutuyor:
KeyError’dan indiği için onun atasını da miras almış oldu. Bir tek yerleştirme kararı, sınıfı
üç ayrı yabancı klozun kapsamına soktu.
Alt satır bunu sayıyor: dört alan sınıfının 2’si yabancı yakalamaya sızıyor. Bu, alandan
habersiz bir kodun int() çağrısı için yazdığı except ValueError klozunun KapsamDisiDeger
nesnesini de yakalayacağı anlamına gelir — ölçüm hatası, bir dönüşüm hatası sanılarak ele
alınır. Ödünç taban, sınıfa anlam kazandırır ve karşılığında kapsam verir.
Otuz çiftin 13’ü tutuyor. Bunların 5’i Exception sütunundan geliyor; her sınıf ondan
iner ve o sütun ayırt edici değildir. Ayırt eden sütunlar kök ile ödünç tabanlardır.
Yakalama Biçimleri ve Kloz Sırası
İkinci ölçüm sınıfları çalıştırır. On iki ölçüm kaydı işlenir; kayıtların bir bölümü geçerli,
bir bölümü alan hatası üretir, biri de program kusuru üretir — katsayı alanı yanlış tiple
yazılmıştır ve çarpma TypeError fırlatır. Dört yakalama biçimi denenir ve her biri için üç
sayı okunur: kaç ad yazıldı, kaç alan hatası ele alındı, kaç program kusuru yutuldu.
- HD14 — On iki kaydın her biri gerçekten işlenir; sonuç sınıfı elle etiketlenmez, fırlatılan nesneden okunur.
- HD15 — Kâhin kurgudur: hangi kaydın alan hatası, hangisinin program kusuru olduğu kayıtların nasıl kurulduğundan bilinir.
- HD16 — “Ele alınan”, yakalanan nesnenin
OlcumHatasiörneği olmasıdır; “yutulan kusur” yakalanan ama alan kökünden inmeyen nesnedir. - HD17 — “Dışarı çıkan”, biçimin klozuna düşmeyip sonraki kloza geçen kayıt sayısıdır.
- HD18 — Kloz sırası ölçümünde iki blok aynı iki sınıfı yazar, yalnız sırayı değiştirir; başka hiçbir fark yoktur.
"""Yakalama bicimleri: kac ad yazilir, kac alan hatasi ele alinir, kac kusur yutulur.""" class OlcumHatasi(Exception): def __init__(self, alan, deger=None): super().__init__(alan) self.alan = alan self.deger = deger def __str__(self): return f"{type(self).__name__}(alan={self.alan!r}, deger={self.deger!r})" class KapsamDisiDeger(OlcumHatasi, ValueError): pass class EksikAlan(OlcumHatasi, KeyError): pass class BozukKayit(OlcumHatasi): pass ALT_SINIR, UST_SINIR = -50.0, 60.0 KAYITLAR = ( {"deger": "12.5", "katsayi": 2}, {"deger": "-3", "katsayi": 1}, {"deger": "99", "katsayi": 1}, {"katsayi": 1}, {"deger": "kuzey", "katsayi": 1}, {"deger": "0", "katsayi": 3}, {"deger": "-80", "katsayi": 1}, {"deger": None, "katsayi": 1}, {"deger": "45", "katsayi": "2"}, {"katsayi": 5}, {"deger": "60", "katsayi": 1}, {"deger": "60.1", "katsayi": 1}, ) def isle(kayit): if "deger" not in kayit: raise EksikAlan("deger") ham = kayit["deger"] try: sayi = float(ham) except (TypeError, ValueError): raise BozukKayit("deger", ham) from None if not ALT_SINIR <= sayi <= UST_SINIR: raise KapsamDisiDeger("deger", sayi) return kayit["katsayi"] * sayi def sinif_adi(kayit): try: isle(kayit) except BaseException as e: return type(e).__name__ return "-" sonuc = [sinif_adi(k) for k in KAYITLAR] print("kayıt sonucu:", ", ".join(f"{i}:{s}" for i, s in enumerate(sonuc, 1))) alan = sum(s in ("KapsamDisiDeger", "EksikAlan", "BozukKayit") for s in sonuc) kusur = sum(s == "TypeError" for s in sonuc) print(f"kayıt {len(KAYITLAR)} | başarılı {sonuc.count('-')} | " f"alan hatası {alan} | program kusuru {kusur}") BICIMLER = ( ("üç ad", (KapsamDisiDeger, EksikAlan, BozukKayit), 3), ("alan kökü", (OlcumHatasi,), 1), ("ödünç taban", (ValueError,), 1), ("geniş", (Exception,), 1), ) print() print(f"{'yakalama biçimi':<17s}{'yazılan ad':>11s}{'ele alınan':>12s}" f"{'yutulan kusur':>15s}{'dışarı çıkan':>14s}") for ad, sinif, adet in BICIMLER: ele = yutulan = disari = 0 for k in KAYITLAR: try: isle(k) except sinif as e: if isinstance(e, OlcumHatasi): ele += 1 else: yutulan += 1 except Exception: disari += 1 print(f" {ad:<15s}{adet:>11d}{ele:>12d}{yutulan:>15d}{disari:>14d}") print() kok_once = {"OlcumHatasi": 0, "KapsamDisiDeger": 0} for k in KAYITLAR: try: isle(k) except OlcumHatasi: kok_once["OlcumHatasi"] += 1 except KapsamDisiDeger: kok_once["KapsamDisiDeger"] += 1 except Exception: pass dar_once = {"KapsamDisiDeger": 0, "OlcumHatasi": 0} for k in KAYITLAR: try: isle(k) except KapsamDisiDeger: dar_once["KapsamDisiDeger"] += 1 except OlcumHatasi: dar_once["OlcumHatasi"] += 1 except Exception: pass print("kloz sırası birinci kloz ikinci kloz") print(f" kök önce OlcumHatasi {kok_once['OlcumHatasi']:<13d}" f" KapsamDisiDeger {kok_once['KapsamDisiDeger']}") print(f" dar önce KapsamDisiDeger {dar_once['KapsamDisiDeger']:<9d}" f" OlcumHatasi {dar_once['OlcumHatasi']}") print() print("özniteliğiyle bildirilen alan hataları:") for i, k in enumerate(KAYITLAR, 1): try: isle(k) except OlcumHatasi as e: print(f" kayıt {i:2d}: {e} -> alan {e.alan!r}, değer {e.deger!r}") except Exception: pass
kayıt sonucu: 1:-, 2:-, 3:KapsamDisiDeger, 4:EksikAlan, 5:BozukKayit, 6:-, 7:KapsamDisiDeger, 8:BozukKayit, 9:TypeError, 10:EksikAlan, 11:-, 12:KapsamDisiDeger kayıt 12 | başarılı 4 | alan hatası 7 | program kusuru 1 yakalama biçimi yazılan ad ele alınan yutulan kusur dışarı çıkan üç ad 3 7 0 1 alan kökü 1 7 0 1 ödünç taban 1 3 0 5 geniş 1 7 1 0 kloz sırası birinci kloz ikinci kloz kök önce OlcumHatasi 7 KapsamDisiDeger 0 dar önce KapsamDisiDeger 3 OlcumHatasi 4 özniteliğiyle bildirilen alan hataları: kayıt 3: KapsamDisiDeger(alan='deger', deger=99.0) -> alan 'deger', değer 99.0 kayıt 4: EksikAlan(alan='deger', deger=None) -> alan 'deger', değer None kayıt 5: BozukKayit(alan='deger', deger='kuzey') -> alan 'deger', değer 'kuzey' kayıt 7: KapsamDisiDeger(alan='deger', deger=-80.0) -> alan 'deger', değer -80.0 kayıt 8: BozukKayit(alan='deger', deger=None) -> alan 'deger', değer None kayıt 10: EksikAlan(alan='deger', deger=None) -> alan 'deger', değer None kayıt 12: KapsamDisiDeger(alan='deger', deger=60.1) -> alan 'deger', değer 60.1
Sayıların Okunması
On iki kaydın 4’ü başarılı, 7’si alan hatası, 1’i program kusuru. Dört biçim bu dağılımı ayrı ayrı bölüyor.
Üç ad ve alan kökü aynı sonucu veriyor: 7 ele alınan, 0 yutulan kusur, 1
dışarı çıkan. İkisi ayırt edilemez — ama biri 3 ad yazıyor, öbürü 1. Kökün ödediği şey
tam olarak budur: alan büyüdükçe yeni somut sınıflar kökten inecek ve except OlcumHatasi
yazan hiçbir kloz değişmeyecek. Üç adı sayan kloz her yeni sınıfta güncellenmek zorundadır.
Ödünç taban yalnız 3 hatayı ele alıyor ve 5 kaydı dışarı bırakıyor.
except ValueError yazmak KapsamDisiDeger’i tutar, ama EksikAlan ile BozukKayit ona
düşmez. Ödünç
taban bir grup adı değildir; her somut sınıfın kendi ödünç tabanı ayrıdır ve aralarında
ortaklık yoktur. Alanın grup adı yalnız köktür.
Geniş yakalama 0 kaydı dışarı bırakıyor — ve 1 program kusurunu yutuyor. Katsayı alanı yanlış tiple yazılmış kayıt, bir alan hatası gibi ele alınıyor. Önceki dersin bulgusu burada somutlaşıyor: kusuru gizleyen şey kusurun kendisi değil, onu kapsayan klozdur.
Kloz sırası tablosu en keskin sayıyı veriyor. Kök önce yazıldığında OlcumHatasi klozu
7 kez, KapsamDisiDeger klozu 0 kez çalışıyor. İkinci kloz yazılmıştır, sözdizimi
doğrudur, hiçbir uyarı vermez ve hiç çalışmaz — çünkü ilk eşleşen kloz kazanır ve kök
zaten hepsini kapsar. Sıra çevrildiğinde dar kloz 3, kök klozu 4 alıyor; toplam yine
7. Yakalama klozları dardan geniş’e yazılır; tersi sessizce ölü kod üretir.
Son döküm özniteliğin ne kazandırdığını gösteriyor. Her satır alan adını ve değeri ileti
ayrıştırmadan veriyor; -80.0 ile 60.1 arasındaki farkı okuyan kod, dizgiyle değil
sayıyla çalışıyor.
Sınırda Çeviri
Kökün ikinci işi tabloda görünmez ama ölçümün kodunda durur. isle işlevi ham değeri
float ile çevirmeye çalışır ve bu çağrı, verinin biçimine göre ValueError ya da
TypeError fırlatabilir. Bu iki sınıfın hiçbiri alanın sınıfı değildir; ikisi de dönüşüm
katmanının sınıfıdır. Kod onları yakalar ve yerine BozukKayit fırlatır:
except (TypeError, ValueError): raise BozukKayit("deger", ham) from None
Bu üç satır bir sınır çevirisidir. İçeride ne olduğunu bilen kod, dışarıya alanın diliyle
konuşur. Çıktının sekizinci kaydı bunun sonucudur: deger alanı sayı değil, hiç değer
değildir ve float ondan TypeError üretir — ama dışarı çıkan sınıf BozukKayit’tir ve
except OlcumHatasi klozu onu tutar. Çeviri yapılmasaydı o kayıt, önceki bölümde “program
kusuru” diye ayrılan kayıtla aynı sınıfa düşer ve ayırt edilemezdi.
Önceki dersin zincir ölçümü buradaki from None’ın bedelini de veriyor: bağlam bastırıldığı
için dışarıdan bakan biri TypeError’ı göremez. Bu bilinçli bir seçimdir — alan katmanının
dışına dönüşüm ayrıntısı sızmaz. Ayrıntının tutulması istenirse from yazılır ve zincir
ikiye çıkar; karar, sınırın hangi tarafına bilgi verileceğidir.
Çevirinin sınırı da vardır. Katsayı alanındaki TypeError çevrilmedi, çünkü o hata dönüşüm
katmanından değil, kaydı kuran koddan geliyor. Bir program kusurunu alan sınıfına
çevirmek, onu ele alınabilir bir şey gibi göstermek olurdu.
Özet
- Özel istisna sınıfının hiyerarşideki yeri bir üslup tercihi değildir; onu kimin yakalayacağını belirler.
- Beş sınıf ile altı yakalama 30 çift üretir, 13’ü tutar; alan kökü 4 sınıfı kapsarken kökten inmeyen sınıfı kapsamaz.
- Ödünç taban sınıfa anlam kazandırır ve karşılığında kapsam verir: dört alan sınıfının
2’si yabancı
except ValueErrorya daexcept KeyErrorklozuna sızar. - Alan kökü 1 adla 7 hatayı ele alır; üç adı sayan kloz aynı sonucu 3 adla verir, ödünç taban yalnız 3 hatayı tutar, geniş kloz 1 program kusurunu yutar.
- Kloz sırası dardan geniş’e olmalıdır: kök önce yazıldığında dar kloz 0 kez çalışır ve sessizce ölü kod hâline gelir.
Sonraki Adım
Bu dersin on iki kaydı bellekte hazır duruyordu; deger alanı zaten bir dizgiydi ve tek
sorun onu sayıya çevirmekti. Kayıtlar bir dosyadan geldiğinde araya bir katman daha girer:
dosyada duran şey ne dizgidir ne sayı, bayttır. Baytın dizgiye çevrilmesi bir kodlama
kararıdır ve o karar yanlışsa hata alan katmanına hiç ulaşmaz — daha okuma sırasında ortaya
çıkar. Sonraki ders aynı baytların metin kipiyle ikili kipte ne verdiğini ve yanlış kodlamanın
tam olarak hangi noktada istisna ürettiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.