İçeriğe geç
academia.sh

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 isinstance ile kapsaması demektir; except deyiminin yaptığı sınama budur.
  • HD4 — Soyağacı zinciri sınıfın çözüm sırasından okunur; object sayı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ığı is ile 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 except bir 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 12 olay tutarken Exception 10, BaseException 11 tutar.
  • Exception yakalaması 6 alan hatasının yanında 3 program kusurunu ve 1 protokol olayını da yutar; BaseException fazladan çıkış isteğini yutar.
  • Çıplak raise aynı nesneyi dışarı çıkarır (zincir 1); sarmalamak ve from yazmak zinciri 2 yapar, from None onu 1’e indirir, yutmak 0’a.
  • finally üç durumda da çalışır, else yalnız istisna oluşmadığında; başarı yolunu else içine almak except deyiminin 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat