İçeriğe geç
academia.sh

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; object sayı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ı issubclass ile kapsaması demektir; except deyiminin 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 ValueError ya da except KeyError klozudur; 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 ValueError ya da except KeyError klozuna 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat