---
title: 'Python Temelleri'
source: 'https://academia.sh/tr/kurslar/python-temelleri'
language: tr
updated: '2026-08-17T04:42:19+00:00'
license: 'CC BY-SA 4.0'
lessons: 16
outcomes:
    - 'Temel sözdizim ve veri tipleriyle program yazar'
    - 'Girinti tabanlı blok yapısını ve deyim/ifade ayrımını kullanır'
    - 'Dosya okuma-yazma ve istisna yönetimini uygular'
---

# Python Temelleri

Python'da sözdizim bir kısaltmadır; kurs her biçimin altında hangi özel yöntemin kaç kez çağrıldığını sayar ve yürütme modelinden istisna hiyerarşisine kadar dilin kendi düzeneğini kurar.

## Dil Temelleri

Yorumlayıcı ve bayt kodu, girintinin dilbilgisel rolü, ad bağlama ile nesne kimliği, işleçlerin çağırdığı özel yöntemler, dizgi biçimlendirme ve tip dönüşümünün dinamik tiplemeye bağlı sonuçları.

### Python'un Yürütme Modeli

Source: https://academia.sh/tr/kurslar/python-temelleri/pythonun-yurutme-modeli

Kaynak önce bütün olarak derlenir sonra yürütülür; sözdizim ise bir kısaltmadır — on bir biçim 12 ayrı özel yöntemi toplam 18 kez çağırır ve üç öğelik nesnede döngü öğe sayısından bir fazla, 4 kez `__next__` çağırır.

Yazılım Geliştirme Pratiği müfredatı beş kursla bir ekip disiplini kurdu ve kapanışında
ölçtüğü her şeyin **dilden bağımsız** olduğunu söyledi: sayılan şey hiçbir zaman yazılan
kodun kendisi değil, onun çevresindeki işti. Bu müfredat çevreden merkeze döner. Sıra dilin
kendisinde.

Programlama Temelleri kursu örneklerini Python'la yazmıştı; atamanın bir bağlama olduğu,
`is` ile `==` ayrımı, temel veri tipleri, işleçler ve tip dönüşümü orada kuruldu. Ama bir
dili **örnek olarak kullanmak** ile **dilin kendisini** öğrenmek ayrı şeylerdir: orada
Python bir anlatım aracıydı, burada ölçülen nesnedir. Bu kurs sözdizimin **altına** bakar ve
tek bir soruyu on altı derste taşır — yazılan biçim, çalışırken hangi düzeneği çağırıyor ve
kaç kez?

#### İki Aşama: Derleme ve Yürütme

Python bir **yorumlayıcı (interpreter)** ile çalışır, ama kaynağı satır satır okuyup hemen
çalıştırmaz. Bir dosya çalıştırıldığında metin önce **sözcük birimlerine (token)** ayrılır,
sonra bir soyut sözdizim ağacına dönüştürülür, sonra **bayt koduna (bytecode)** çevrilir.
Yorumlayıcının yürüttüğü şey bayt koddur, kaynak metin değil.

İki aşama arasındaki sınır gözlenebilir. Derleme, dosyanın **tamamı** üzerinde yapılır:
kaynağın son satırındaki bir sözdizim kusuru, ilk satırdaki deyimin yürütülmesini de
engeller. Yürütme aşamasında doğan bir hata ise ancak kendi sırası geldiğinde ortaya çıkar
ve ondan öncekiler çalışmış olur. Aradaki fark iki yerleşik işlevle ölçülebilir: `compile`
kaynağı derler ve bir kod nesnesi döndürür, `exec` o nesneyi yürütür.

```python
"""Iki asama: kaynak once bir butun olarak derlenir, sonra yurutulur."""

KAYNAK = {
    "eksik parantez": 'yaz = print\nyaz("bir")\nyaz(\n',
    "tanimsiz ad": 'yaz = print\nyaz("bir")\nyazz("iki")\n',
}

for ad, kaynak in KAYNAK.items():
    print(f"--- {ad}")
    try:
        kod = compile(kaynak, "<ders>", "exec")
    except SyntaxError as e:
        print(f"derleme: {type(e).__name__} — hiçbir deyim yürütülmedi")
        continue
    print(f"derleme: sorunsuz, elde edilen {type(kod).__name__} nesnesi")
    try:
        exec(kod, {})
    except Exception as e:
        print(f"yürütme: {type(e).__name__} — kendinden önceki deyimler yürütüldü")
```

```
--- eksik parantez
derleme: SyntaxError — hiçbir deyim yürütülmedi
--- tanimsiz ad
derleme: sorunsuz, elde edilen code nesnesi
bir
yürütme: NameError — kendinden önceki deyimler yürütüldü
```

İkinci kaynakta `bir` satırı basılmış, ondan sonraki çağrı `NameError` ile düşmüştür.
Birinci kaynakta hiçbir şey basılmamıştır — oysa oradaki ilk iki satır kusursuzdur. Ayrım
şudur: **sözdizim kusuru derleme aşamasında, ad kusuru yürütme aşamasında bulunur.** Bir
adın var olup olmadığı derlenirken bilinmez, çünkü ad ancak yürütme sırasında bir nesneye
bağlanır.

Aynı iki adım **etkileşimli kabukta (interactive shell)** da işler; orada derlenen birim
dosyanın tamamı değil, girilen bloktur. Bloğun sözdizimi bozuksa blok hiç yürütülmez.

Derlemenin ürünü olan bayt kodun düzeni dilin tanımının parçası değildir; yorumlayıcının iç
işidir ve okunması bu kursun konusu değil. Okunacak olan şey bayt kodun **çağırdığı**
yöntemlerdir.

#### Sözdizim Bir Kısaltmadır

Kursun ölçü ekseni tek bir gözlemden çıkar: Python'da sözdizimin çoğu biçimi bir
**kısaltmadır**. `n + m` yazıldığında yürütülen şey bir toplama değil, bir **çağrıdır** —
`n` nesnesinin `__add__` yöntemi çağrılır ve dönen değer ifadenin değeridir. `for x in n`
bir döngü sözcüğü değil, iki yöntemin ardışık çağrısıdır: önce `__iter__`, sonra tükenene
kadar `__next__`.

Bu yöntemlerin adları dilin tanımında yazılıdır; her birine **özel yöntem (special method)**,
bir biçimin gerektirdiği yöntem kümesine de **protokol** denir. Adları iki alt çizgi
arasında yazılır ve bu, onları sıradan yöntemlerden ayırmak içindir: özel yöntemler
doğrudan çağrılmak için değil, **sözdizim tarafından çağrılmak için** vardır.

Her sözdizim biçimi bir kısaltma değildir ve bu ayrım baştan konmalıdır. Bir ada değer
bağlamak, bir işlev tanımlamak ya da bir değer döndürmek nesneye hiçbir soru sormaz; bu
biçimler adlarla ve akışla ilgilenir, nesnenin içine bakmaz. Kısaltma olanlar, bir nesneyi
**tüketen** biçimlerdir: onu toplayan, içinde dolaşan, bir koşulda sınayan, dilimleyen,
metne çeviren. Ölçülen on bir biçimin tamamı bu ikinci kümedendir.

Bunun doğrudan bir sonucu var: **sözdizim tipin adına bakmaz, yöntemin varlığına bakar.**
`+` işlecinin sayılarda toplama, dizgilerde birleştirme yapması bir ayrıcalık değil, iki
ayrı tipin aynı adlı yöntemi ayrı biçimde tanımlamasıdır. Programlama Temelleri kursunda bu
gözlem **işleç aşırı yükleme** başlığıyla bir kavram olarak kuruldu; burada ölçülen şey
kavram değil, onu gerçekleştiren **addır** — hangi biçim hangi yöntemi kaç kez çağırıyor.

#### Ortak Kurgu: Katılımını Kaydeden Nesne

Ölçüm için tek bir sınıf yeter. `Izci`, çok sayıda özel yöntemi tanımlar ve her yöntem
çağrıldığında adını ortak bir listeye yazar. Üzerinde sıradan sözdizim çalıştırılır — `for`,
`in`, `if`, `+`, `+=`, dilim, `str()`, `with`, `==` — ve her biçimin hangi yöntemi kaç kez
çağırdığı listeden okunur.

İkinci bir sınıf, `Bosluksuz`, `Izci`'den türer ve yalnız tek bir şey ekler: `__bool__`.
Aynı `if` ifadesi bu nesnede artık başka bir yöntemi çağıracaktır. Kâhin kurgunun
kendisidir: hangi yöntemin çağrıldığını nesne kendi kaydettiği için biliriz, dolayısıyla
sayı bir çıkarım değil bir dökümdür.

```python
"""Sozdizimin cagirdigi protokol: her ozel yontem cagrildiginda kaydedilir."""

KAYIT = []


def kaydet(ad):
    KAYIT.append(ad)


class Izci:
    """Cok sayida protokole katilan bir nesne; her katilimi kaydeder."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __iter__(self):
        kaydet("__iter__")
        self._i = 0
        return self

    def __next__(self):
        kaydet("__next__")
        if self._i >= len(self.ogeler):
            raise StopIteration
        deger = self.ogeler[self._i]
        self._i += 1
        return deger

    def __len__(self):
        kaydet("__len__")
        return len(self.ogeler)

    def __contains__(self, x):
        kaydet("__contains__")
        return x in self.ogeler

    def __add__(self, o):
        kaydet("__add__")
        return Izci(self.ogeler + list(o.ogeler))

    def __iadd__(self, o):
        kaydet("__iadd__")
        self.ogeler.extend(o.ogeler)
        return self

    def __getitem__(self, k):
        kaydet("__getitem__")
        return self.ogeler[k]

    def __eq__(self, o):
        kaydet("__eq__")
        return isinstance(o, Izci) and self.ogeler == o.ogeler

    def __str__(self):
        kaydet("__str__")
        return f"Izci{tuple(self.ogeler)}"

    def __repr__(self):
        kaydet("__repr__")
        return self.__str__()

    def __enter__(self):
        kaydet("__enter__")
        return self

    def __exit__(self, tur, deger, iz):
        kaydet("__exit__")
        return False          # istisnayi yutmaz


class Bosluksuz(Izci):
    """__bool__ tanimli: dogruluk sinamasi artik __len__'e dusmez."""

    def __bool__(self):
        kaydet("__bool__")
        return len(self.ogeler) > 0


def olc(islev):
    """Bir sozdizim bicimini calistirir ve cagrilan protokolleri dondurur."""
    KAYIT.clear()
    try:
        islev()
    except Exception as e:
        KAYIT.append(f"!{type(e).__name__}")
    return list(KAYIT)


def dongu():
    for _ in Izci():
        pass


def uyelik():
    9 in Izci()


def dogruluk_len():
    if Izci():
        pass


def dogruluk_bool():
    if Bosluksuz():
        pass


def toplama():
    Izci() + Izci()


def yerinde_toplama():
    a = Izci()
    a += Izci()


def dilim():
    Izci()[0:2]


def metne_cevirme():
    str(Izci())


def baglam():
    with Izci():
        pass


def baglam_istisnali():
    with Izci():
        raise ValueError("ornek")


def esitlik():
    Izci() == Izci()


BICIMLER = (
    ("for x in n", dongu),
    ("x in n", uyelik),
    ("if n  (__len__)", dogruluk_len),
    ("if n  (__bool__)", dogruluk_bool),
    ("n + m", toplama),
    ("n += m", yerinde_toplama),
    ("n[0:2]", dilim),
    ("str(n)", metne_cevirme),
    ("with n", baglam),
    ("with n + istisna", baglam_istisnali),
    ("n == m", esitlik),
)
```

Ölçümün varsayımları:

- **DT1** — Ölçülen biçimler on bir tanedir; dilin bütün sözdizimi değil, bu kursun ders
  ders ödeyeceği çekirdektir. Kalan biçimler kendi derslerinde ölçülür.
- **DT2** — Kâhin nesnenin kendi kaydıdır: bir yöntem çağrıldığında adını listeye yazar,
  dolayısıyla ölçüm bir çıkarım değil bir sayımdır.
- **DT3** — `Izci` üç öğeyle kurulur; döngü ölçümünde okunacak çağrı sayısı bu üçe bağlıdır.
- **DT4** — `__exit__` her koşumda `False` döndürür; bağlam bloğunda doğan istisna yutulmaz
  ve ölçüme, önüne bir ünlem işareti konmuş bir istisna adı olarak girer.
- **DT5** — Ölçüm hiçbir ortam verisi okumaz: bellek adresi, nesne kimliği sayısı ve süre
  kullanılmaz; sayılan tek şey çağrılan yöntem adlarıdır.
- **DT6** — Her biçim iki kez ölçülür — tablo için bir, çift karşılaştırması için bir.
  `Izci` her ölçümde yeniden kurulduğundan iki koşum aynı diziyi verir.

#### Ölçüm

```python
print(f"{'sözdizim':<20s} {'çağrı':>6s}  protokol dizisi")
for ad, islev in BICIMLER:
    c = olc(islev)
    print(f"{ad:<20s} {len(c):6d}  {' '.join(c)}")

print()
ogeli = {ad: olc(f) for ad, f in BICIMLER}
tekil = sorted({p for c in ogeli.values() for p in c if not p.startswith("!")})
print(f"ayrı protokol {len(tekil)}: {', '.join(tekil)}")
print(f"toplam çağrı {sum(len(c) for c in ogeli.values())}, "
      f"istisnayla biten biçim "
      f"{sum(1 for c in ogeli.values() if any(p.startswith('!') for p in c))}")

print()
print("aynı görünen, ayrı protokol çağıran çiftler:")
for a, b in (("n + m", "n += m"),
             ("if n  (__len__)", "if n  (__bool__)")):
    print(f"  {a:<18s} -> {' '.join(ogeli[a])}")
    print(f"  {b:<18s} -> {' '.join(ogeli[b])}")

print()
dongu_kaydi = ogeli["for x in n"]
print(f"üç öğelik nesnede döngü: {dongu_kaydi.count('__next__')} kez __next__ "
      f"çağrıldı — öğe sayısından bir fazla; sonuncusu StopIteration ile bitti")
```

```
sözdizim              çağrı  protokol dizisi
for x in n                5  __iter__ __next__ __next__ __next__ __next__
x in n                    1  __contains__
if n  (__len__)           1  __len__
if n  (__bool__)          1  __bool__
n + m                     1  __add__
n += m                    1  __iadd__
n[0:2]                    1  __getitem__
str(n)                    1  __str__
with n                    2  __enter__ __exit__
with n + istisna          3  __enter__ __exit__ !ValueError
n == m                    1  __eq__

ayrı protokol 12: __add__, __bool__, __contains__, __enter__, __eq__, __exit__, __getitem__, __iadd__, __iter__, __len__, __next__, __str__
toplam çağrı 18, istisnayla biten biçim 1

aynı görünen, ayrı protokol çağıran çiftler:
  n + m              -> __add__
  n += m             -> __iadd__
  if n  (__len__)    -> __len__
  if n  (__bool__)   -> __bool__

üç öğelik nesnede döngü: 4 kez __next__ çağrıldı — öğe sayısından bir fazla; sonuncusu StopIteration ile bitti
```

#### Sayıların Okunması

On bir biçim **12 ayrı protokolü** ve toplam **18 çağrıyı** üretiyor. Bu, kursun taşıyacağı
ölçüdür: bir dersin sayısı, anlattığı sözdizimin hangi protokolü kaç kez çağırdığıdır.
Tablonun her satırı bir sözdizim biçimini, karşısındaki dizi de o biçimin altında gerçekten
çalışan çağrıları gösteriyor. Hiçbiri tahmin değil; hepsi nesnenin kendi kaydından okundu.

**Aynı görünen iki ifade ayrı protokol çağırır.** `n + m` ile `n += m` yazımca birbirinin
kısaltması gibi durur, ama biri `__add__`, öteki `__iadd__` çağırıyor. Fark yalnız ad farkı
değildir: `__add__` **yeni bir nesne** üretip döndürür, `__iadd__` **var olan nesneyi
değiştirip** kendisini döndürür. Aynı biçimde `if n`, `Izci` üzerinde `__len__`, `Bosluksuz`
üzerinde `__bool__` çağırıyor — tek bir sözdizim, iki ayrı yöntem. Ayrımı yaratan şey
nesnenin hangi yöntemi tanımladığıdır. **Sözdizimin görünüşü çağrılan protokolü söylemez.**
Bu iddianın ilk yarısı bu konunun işleçler dersinde, ikinci yarısı akış konusunun koşullar
dersinde ödenecek.

**İstisna bir hata değil, protokolün parçasıdır.** Üç öğelik nesnede döngü **4 kez**
`__next__` çağırdı — öğe sayısından bir fazla. Dördüncü çağrı bir değer döndürmez,
`StopIteration` fırlatır; döngüyü bitiren şey tam olarak budur. `for` sözcüğü öğelerin
bittiğini saymayla değil, bir istisnayı yakalamayla anlar. `with n + istisna` satırı aynı
sınıftandır: blok içinde bir istisna doğduğu hâlde `__exit__` **yine çağrılmıştır** ve
kayıtta üçüncü öğe olarak istisnanın adı durur. `__exit__` `False` döndürdüğü için istisna
dışarı çıktı — dönseydi `True`, çıkmayacaktı.

#### Protokol Yoksa Sözdizim de Yoktur

Yukarıdaki tablo `Izci`'nin yöntemleri **olduğu için** doludur. Aynı biçimler hiçbir özel
yöntem tanımlamayan bir nesne üzerinde denenirse ne olur? Ölçümün varsayımı:

- **DT7** — Karşılaştırılan nesne hiçbir özel yöntem tanımlamaz; taşıdığı tek şey her
  nesnenin tabanından gelen tanımlardır. Düşen biçimler istisna türüyle, düşmeyenler
  "çalıştı" ile yazılır; **değerleri basılmaz**, çünkü taban tanımın ürettiği metin ortama
  bağlı bir kimlik taşır.

```python
class Ciplak:
    """Hicbir ozel yontem tanimlamaz; nesnenin tabanindan geleni tasir."""


def ciplak_dongu():
    for _ in Ciplak():
        pass


def ciplak_baglam():
    with Ciplak():
        pass


CIPLAK_BICIMLER = (
    ("for x in n", ciplak_dongu),
    ("x in n", lambda: 9 in Ciplak()),
    ("n + m", lambda: Ciplak() + Ciplak()),
    ("n[0:2]", lambda: Ciplak()[0:2]),
    ("len(n)", lambda: len(Ciplak())),
    ("with n", ciplak_baglam),
    ("str(n)", lambda: str(Ciplak())),
    ("n == m", lambda: Ciplak() == Ciplak()),
)

print("özel yöntemi olmayan nesnede aynı biçimler:")
dusen = 0
for ad, islev in CIPLAK_BICIMLER:
    try:
        islev()
    except Exception as e:
        dusen += 1
        print(f"  {ad:12s} {type(e).__name__}")
    else:
        print(f"  {ad:12s} çalıştı — taban nesneden gelen tanım kullanıldı")
print(f"düşen biçim {dusen} / {len(CIPLAK_BICIMLER)}")
```

```
özel yöntemi olmayan nesnede aynı biçimler:
  for x in n   TypeError
  x in n       TypeError
  n + m        TypeError
  n[0:2]       TypeError
  len(n)       TypeError
  with n       TypeError
  str(n)       çalıştı — taban nesneden gelen tanım kullanıldı
  n == m       çalıştı — taban nesneden gelen tanım kullanıldı
düşen biçim 6 / 8
```

Sekiz biçimin **6**'sı düşüyor ve hepsi aynı istisnayla: `TypeError`. Adı yerindedir —
kusur, kullanılan değerde değil, nesnenin **o sözdizimi karşılayacak yöntemi
taşımamasındadır**. Kaynak metin kusursuzdur ve derleme aşamasını sorunsuz geçmiştir; iş
yürütmeye geldiğinde çağrılacak yöntem bulunamaz.

Düşmeyen **2** biçim de aynı kuralın öbür yüzüdür. `str(n)` ile `n == m` çalışıyor, çünkü
her nesne bu iki yöntemi **tabanından** devralır: birincisi nesneyi tanıtan bir metin
üretir, ikincisi eşitliği kimlik üzerinden yanıtlar. Yani protokol yine vardır; yalnız
tanımı sınıfın kendisinden değil, tabanından gelir. Bir sınıf `__str__` yazdığında sıfırdan
bir yetenek eklemez, devraldığı tanımın **yerine geçer**.

Sonuç, kursun taşıyacağı dördüncü okumadır: Python'da bir nesnenin "ne yapabildiği" tipinin
adıyla değil, **erişebildiği özel yöntemlerle** belirlenir. `__iter__` erişilebilirse
üzerinde döngü kurulur, `__len__` erişilebilirse uzunluğu sorulur; ikisi arasında bir
zorunluluk yoktur. Dil, bir biçimi çalıştırmadan önce tipin adına bakmaz — yöntemi arar,
bulamazsa `TypeError` fırlatır.

#### Özet

- Kaynak metin önce sözcük birimlerine ve soyut sözdizim ağacına, sonra bayt koda çevrilir;
  yorumlayıcının yürüttüğü şey bayt koddur.
- Derleme dosyanın tamamı üzerinde yapılır: sözdizim kusuru hiçbir deyimi çalıştırmaz, ad
  kusuru ise ancak kendi sırası gelince ortaya çıkar ve öncekiler çalışmış olur.
- Sözdizim bir kısaltmadır; her biçim adı belli bir özel yöntemi çağırır ve yöntem kümesine
  protokol denir.
- On bir biçim **12 ayrı protokolü** toplam **18 çağrıyla** kullanır; `for x in n` tek başına
  5 çağrı üretir.
- Üç öğelik nesnede `__next__` **4 kez** çağrılır ve sonuncusu `StopIteration` fırlatır;
  istisna burada bir kusur değil, döngüyü bitiren düzenektir.
- Hiçbir özel yöntem tanımlamayan bir nesnede aynı sekiz biçimin **6**'sı `TypeError` ile
  düşer; kalan **2**'si taban nesneden devralınan tanımla çalışır.

#### Sonraki Adım

Bu derste sözdizimin altındaki çağrılar sayıldı, ama sözdizimin kendisi hiç sorgulanmadı:
`for` satırının altındaki gövdenin nereden başlayıp nerede bittiğini ne belirliyor? Python
bunu ayraçla değil **boşlukla** yapar ve boşluk burada bir yazım tercihi değil, dilbilgisinin
parçasıdır. Sonraki ders bunu ölçer: aynı sözcük birimi dizisi, yalnızca girintisi
değiştirilerek farklı bir ağaca ve farklı bir sonuca nasıl dönüşüyor — ve bir deyim ile bir
ifade arasındaki sınır tam olarak nerede?

### Sözdizim ve Girinti

Source: https://academia.sh/tr/kurslar/python-temelleri/sozdizim-ve-girinti

Girinti bir yazım tercihi değil dilbilgisinin parçasıdır: aynı 21 içerik birimi iki ayrı girintiyle iki ayrı ağaç ve iki ayrı sonuç üretir, dört ayrı genişlik tek bir ağaç verir ve on parçadan yalnız 5'i ifadedir.

Önceki ders kaynağın önce bütün olarak derlendiğini gösterdi ve derlemenin ilk adımının
metni **sözcük birimlerine** ayırmak olduğunu söyledi. O adımda bir soru açıkta kaldı:
satır başındaki boşluğa ne oluyor?

Ayraçla blok kuran dillerin çoğunda hiçbir şey — boşluk atılır, gövdenin nerede başlayıp
nerede bittiğini ayraçlar söyler ve girinti yalnızca okuyucu için konur. Python'da boşluk
atılmaz. **Girinti (indentation)** birim dizisine kendi birimlerini ekler ve blok yapısını
tam olarak o birimler kurar. Bunun gözlenebilir sonucu şudur: aynı sözcük birimi dizisi,
yalnız girintisi değiştirilerek başka bir ağaca ve başka bir sonuca dönüşür.

#### Bloğu Ayraç Değil Boşluk Kurar

Birimlere ayırma adımı her satırın başındaki boşluğu ölçer ve bir yığıt üzerinde tutar. Bir
satır bir öncekinden daha içerideyse akışa bir `INDENT` birimi konur; daha dışarıdaysa,
kapanan her düzey için bir `DEDENT` birimi konur. Birden çok düzey aynı anda kapanıyorsa o
sayıda `DEDENT` üretilir, dolayısıyla dizideki `DEDENT` sayısı kapanan blok sayısını verir.
Bu birimlerin kaynak metinde karşılığı olan hiçbir karakter yoktur — boşluğun kendisinden
üretilirler.

Aşağıdaki iki kaynak, birimlerin adına bakıldığında ayırt edilemez. Yalnız bir satırın
girintisi farklıdır: `print` çağrısı birinde döngü gövdesinin dışında, ötekinde içindedir.

Ölçümün varsayımları:

- **DT8** — İki kaynak yalnız son satırın girintisiyle ayrılır; hiçbir karakteri
  eklenmemiş, çıkarılmamış ya da değiştirilmiştir.
- **DT9** — Karşılaştırma birimlerin **adları** üzerinden yapılır; `INDENT`, `DEDENT`,
  `NEWLINE`, `NL` ve dosya sonu birimi "düzen birimi" sayılıp ayrı tutulur, kalanlar
  "içerik birimi" sayılır.
- **DT10** — Ağaç, çözümlenen yapının en üst düzeyindeki deyimlerin sınıf adlarıyla
  yazılır; döngü deyiminin gövdesi köşeli ayraç içinde verilir. Ağacın tam dökümü değil,
  bu ölçüme yeten kabuğu okunur.
- **DT11** — Yürütme çıktısı ayrı bir tampona alınır ve basılan satırların listesi olarak
  yazılır; ortama bağlı hiçbir değer okunmaz.

```python
"""Ayni sozcuk birimi dizisi, iki ayri girinti: uc gorunum."""
import ast
import contextlib
import io
import tokenize

DIS = "toplam = 0\nfor x in (1, 2, 3):\n    toplam += x\nprint(toplam)\n"
IC = "toplam = 0\nfor x in (1, 2, 3):\n    toplam += x\n    print(toplam)\n"
KAYNAK = {"gövde dışında": DIS, "gövde içinde": IC}
DUZEN = {"INDENT", "DEDENT", "NEWLINE", "NL", "ENDMARKER"}


def birimler(kaynak):
    ak = io.StringIO(kaynak).readline
    return [tokenize.tok_name[t.type] for t in tokenize.generate_tokens(ak)]


def agac(kaynak):
    parcalar = []
    for d in ast.parse(kaynak).body:
        ad = type(d).__name__
        if isinstance(d, ast.For):
            ad += "[" + ",".join(type(g).__name__ for g in d.body) + "]"
        parcalar.append(ad)
    return " ".join(parcalar)


def calistir(kaynak):
    tampon = io.StringIO()
    with contextlib.redirect_stdout(tampon):
        exec(compile(kaynak, "<ders>", "exec"), {})
    return tampon.getvalue().split()


for ad, kaynak in KAYNAK.items():
    tumu = birimler(kaynak)
    icerik = [b for b in tumu if b not in DUZEN]
    print(f"{ad:14s} birim {len(tumu)}  düzen dışı {len(icerik)}  "
          f"INDENT/DEDENT {[i for i, b in enumerate(tumu) if b in ('INDENT', 'DEDENT')]}")

a, b = (birimler(k) for k in KAYNAK.values())
print(f"düzen birimleri atılınca iki dizi eşit mi: "
      f"{[x for x in a if x not in DUZEN] == [x for x in b if x not in DUZEN]}")
print()
for ad, kaynak in KAYNAK.items():
    print(f"{ad:14s} ağaç: {agac(kaynak)}")
    print(f"{'':14s} basılan: {calistir(kaynak)}")
```

```
gövde dışında  birim 28  düzen dışı 21  INDENT/DEDENT [16, 21]
gövde içinde   birim 28  düzen dışı 21  INDENT/DEDENT [16, 26]
düzen birimleri atılınca iki dizi eşit mi: True

gövde dışında  ağaç: Assign For[AugAssign] Expr
               basılan: ['6']
gövde içinde   ağaç: Assign For[AugAssign,Expr]
               basılan: ['1', '3', '6']
```

İki kaynak da **28** birim üretiyor ve bunların **21**'i içerik birimi. Düzen birimleri
atıldığında kalan iki dizi **birebir eşit** — adları da sıraları da aynı. Ayrım tek bir
birimin yerindedir: `DEDENT` birinde **21.**, ötekinde **26.** sırada duruyor. Kaynakta
değişen tek şey dört boşluktur ve o dört boşluk bir birimi beş sıra öteye taşımıştır.

Bu tek fark ağacı değiştiriyor. Birinci kaynakta çağrı, döngünün **kardeşi**: ağaçta
`For[AugAssign]` bittikten sonra ayrı bir `Expr` geliyor. İkincisinde çağrı döngünün
**çocuğu**: `For[AugAssign,Expr]`. Sonuç da buna göre ayrışıyor — biri tek satır, öteki üç
satır basıyor.

Buradan çıkan kural, dile giriş için en pahalı yanlış anlamalardan birini kapatır:
**girintiyi bozmak bir üslup kusuru değil, programı değiştirmektir.** Ayraçlı bir dilde
aynı iki kaynak birbirinin biçimlendirilmiş hâli olurdu ve ikisi de aynı şeyi yapardı.
Burada girinti tek belirleyicidir; başka hiçbir işaret gövdenin sınırını söylemez.

#### Genişlik Serbest, Tutarlılık Zorunlu

Girintinin dilbilgisel olması, belli bir **genişliğin** dilbilgisel olduğu anlamına gelmez.
Dil bir sayı dayatmaz; dayattığı şey bir bloğun kendi içinde tutarlı olmasıdır. Sınır
gözlenebilir: aynı yapı dört ayrı genişlikle yazılıp ağaçları karşılaştırılabilir, sonra
tutarsız dört kaynak derlenmeye çalışılabilir.

- **DT12** — Doğru kaynaklarda genişlik değişir ama blok içinde tutarlı kalır; bozuk
  kaynaklarda tutarlılık dört ayrı biçimde kırılır. Kaynaklar derlenir, **yürütülmez** —
  ölçülen şey derleme aşamasının kabul edip etmediğidir.

```python
"""Girintinin kurali: genislik serbest, tutarlilik zorunlu."""
import ast

GENISLIK = {"iki boşluk": "if 1:\n  a = 1\n  b = 2\n",
            "dört boşluk": "if 1:\n    a = 1\n    b = 2\n",
            "sekiz boşluk": "if 1:\n        a = 1\n        b = 2\n",
            "sekme": "if 1:\n\ta = 1\n\tb = 2\n"}

BOZUK = {"girinti hiç yok": "for x in (1, 2):\nb = 1\n",
         "beklenmeyen girinti": "a = 0\n    b = 1\n",
         "hizalanmayan çıkış": "if 1:\n        a = 1\n    b = 2\n",
         "sekme ile boşluk karışık": "if 1:\n\ta = 1\n        b = 2\n"}


def agac(kaynak):
    d = ast.parse(kaynak).body[0]
    return f"{type(d).__name__}[{','.join(type(g).__name__ for g in d.body)}]"


print("aynı yapı, ayrı genişlik:")
for ad, kaynak in GENISLIK.items():
    print(f"  {ad:12s} ağaç: {agac(kaynak)}")
print(f"  dört ağacın hepsi aynı mı: "
      f"{len({agac(k) for k in GENISLIK.values()}) == 1}")

print("\ntutarsız girinti:")
for ad, kaynak in BOZUK.items():
    try:
        compile(kaynak, "<ders>", "exec")
        print(f"  {ad:24s} derlendi")
    except SyntaxError as e:
        print(f"  {ad:24s} {type(e).__name__}")
print(f"  IndentationError bir SyntaxError mı: "
      f"{issubclass(IndentationError, SyntaxError)}")
print(f"  TabError bir IndentationError mı: "
      f"{issubclass(TabError, IndentationError)}")
```

```
aynı yapı, ayrı genişlik:
  iki boşluk   ağaç: If[Assign,Assign]
  dört boşluk  ağaç: If[Assign,Assign]
  sekiz boşluk ağaç: If[Assign,Assign]
  sekme        ağaç: If[Assign,Assign]
  dört ağacın hepsi aynı mı: True

tutarsız girinti:
  girinti hiç yok          IndentationError
  beklenmeyen girinti      IndentationError
  hizalanmayan çıkış       IndentationError
  sekme ile boşluk karışık TabError
  IndentationError bir SyntaxError mı: True
  TabError bir IndentationError mı: True
```

Dört genişlik tek bir ağaç veriyor. Dilbilgisel olan şey girintinin **miktarı** değil,
**yapısı**: bir düzeyin açılıp aynı hizada sürmesi ve kapanırken daha önce açılmış bir
hizaya oturması. İki boşlukla yazılmış program ile sekiz boşlukla yazılmış program aynı
programdır; aralarındaki seçim ekip sözleşmesine kalır, dile değil.

Tutarlılık kırıldığında dördünde de derleme durur. Üçü `IndentationError` verir: gövdesi
hiç açılmayan bir blok, hiçbir şeyi açmadan içeri kayan bir satır ve daha önce açılmış
hiçbir hizaya oturmayan bir çıkış. Dördüncüsü ayrılır — sekme ile boşluğun karıştığı
kaynak `TabError` verir, çünkü bu iki karakterin genişliği kaynağa bakarak çözülemez.
Çıktının son iki satırı üçünün de tek bir soydan geldiğini gösteriyor: `TabError` bir
`IndentationError`, o da bir `SyntaxError`.

Bu soy zinciri önceki dersin sınırını da yerine oturtuyor. Girinti kusurları **derleme
aşamasında** bulunur; hiçbiri yürütmeye kalmaz. Dosyanın son satırındaki bozuk bir girinti,
ilk satırdaki atamanın da yapılmamasına yol açar.

#### Mantıksal Satır ile Fiziksel Satır

Girintinin dilbilgisel olması bir yerde biter ve sınırı bilmek gerekir. Blok yapısını kuran
şey **mantıksal satırın** başındaki boşluktur; ekrandaki her satır başı bir mantıksal satır
başlatmaz. Bir ifade açılmış bir ayracın içindeyken satır sonu deyimi bitirmez, akışa
`NEWLINE` yerine `NL` konur ve **o satırların girintisi hiçbir birim üretmez**. Aynı
süreklilik ters bölü ile de kurulabilir.

- **DT13** — Dört biçim aynı toplamı yazar ve yalnız satırlara bölünüşleriyle ayrılır;
  ölçülen şey fiziksel satır sayısı, mantıksal satır sonu (`NEWLINE`), süreklilik satır
  sonu (`NL`) ve `INDENT` sayısıdır. Değer sütunu kaynağın kendi ürettiği toplamdır.

```python
"""Mantiksal satir ile fiziksel satir: ayrac icinde girinti dilbilgisel degildir."""
import ast
import io
import tokenize

BICIM = {
    "tek satır": "toplam = 1 + 2 + 3\n",
    "ayraç içinde hizalı": "toplam = (1 +\n          2 +\n          3)\n",
    "ayraç içinde savruk": "toplam = (1 +\n2 +\n              3)\n",
    "ters bölü ile": "toplam = 1 + \\\n    2 + \\\n    3\n",
}


def say(kaynak):
    ak = io.StringIO(kaynak).readline
    adlar = [tokenize.tok_name[t.type] for t in tokenize.generate_tokens(ak)]
    return adlar.count("NEWLINE"), adlar.count("NL"), adlar.count("INDENT")


print(f"{'biçim':<21s} {'fiziksel':>8s} {'NEWLINE':>7s} {'NL':>3s} {'INDENT':>6s}"
      f"  ağaç     değer")
for ad, kaynak in BICIM.items():
    mantiksal, nl, ind = say(kaynak)
    ortam = {}
    exec(compile(kaynak, "<ders>", "exec"), ortam)
    agac = " ".join(type(d).__name__ for d in ast.parse(kaynak).body)
    print(f"{ad:<21s} {len(kaynak.splitlines()):8d} {mantiksal:7d} {nl:3d} {ind:6d}"
          f"  {agac:8s} {ortam['toplam']}")
```

```
biçim                 fiziksel NEWLINE  NL INDENT  ağaç     değer
tek satır                    1       1   0      0  Assign   6
ayraç içinde hizalı          3       1   2      0  Assign   6
ayraç içinde savruk          3       1   2      0  Assign   6
ters bölü ile                3       1   0      0  Assign   6
```

Dört biçim de tek bir `Assign` düğümü ve tek bir değer veriyor. Üçü **3** fiziksel satıra
yayıldığı hâlde hepsinde `NEWLINE` sayısı **1**: mantıksal satır bir tanedir. Ayraçlı iki
biçimde aradaki satır sonları `NL` olarak, yani "burada bir deyim bitmedi" işaretiyle
geçiyor. Ters bölülü biçimde satır sonu birime hiç dönüşmüyor; süreklilik daha metin
birimlere ayrılmadan çözülüyor.

En çok söyleyen sütun `INDENT`: dördünde de **0**. "Savruk" biçimin ikinci satırı sütun
başından, üçüncüsü çok içeriden başlıyor ve hiçbiri bir şey değiştirmiyor. Demek ki girinti
dilbilgisel olmayı **ayracın içinde bırakır**; orada yapılan hizalama tümüyle okuyucu
içindir ve tutarsızlığı bir kusur üretmez. Kural şöyle daraltılabilir: girinti yalnız bir
mantıksal satırın başında anlam taşır.

#### Deyim ve İfade

Girinti bir blok kurar; blok ise **deyimlerden** oluşur. Bu, dilin ikinci temel ayrımını
gerektirir. **İfade (expression)** bir değer üretir: hesaplanır ve geriye bir nesne bırakır.
**Deyim (statement)** bir iş yapar: bir ad bağlar, bir bloğa girer, akışı yönlendirir; geriye
bir değer bırakmaz.

Ayrım sezgiye bırakılmak zorunda değildir, çünkü derleme kipi onu doğrudan söyler. `eval`
kipi yalnız bir ifade kabul eder; `exec` kipi bir deyim dizisi kabul eder.

- **DT14** — Her parça iki kipte de derlenmeye çalışılır; ölçülen şey kabul edilip
  edilmediğidir. Değeri yazılan parçalar tek bir adın bağlı olduğu bir ortamda
  değerlendirilir, dolayısıyla değer sütunu ortama değil o bağlamaya bağlıdır.

```python
"""Deyim mi ifade mi: 'eval' kipi yalniz ifadeyi derler, 'exec' kipi ikisini de."""

PARCALAR = (
    "2 + 3",
    "toplam",
    "len('olcum')",
    "[k for k in (1, 2, 3)]",
    "toplam if toplam else 0",
    "toplam = 5",
    "toplam += 1",
    "if toplam: pass",
    "for k in (1, 2): pass",
    "pass",
)

print(f"{'parça':<26s} {'ifade':>5s} {'deyim':>5s}  değeri")
ifade_sayisi = deyim_sayisi = 0
for p in PARCALAR:
    try:
        kod = compile(p, "<ders>", "eval")
        ifade = True
    except SyntaxError:
        ifade = False
    try:
        compile(p, "<ders>", "exec")
        deyim = True
    except SyntaxError:
        deyim = False
    ifade_sayisi += ifade
    deyim_sayisi += deyim
    deger = repr(eval(kod, {"toplam": 3})) if ifade else "—"
    print(f"{p:<26s} {str(ifade):>5s} {str(deyim):>5s}  {deger}")

print(f"\n{len(PARCALAR)} parçadan ifade {ifade_sayisi}, deyim {deyim_sayisi}; "
      f"yalnız deyim olan {deyim_sayisi - ifade_sayisi}")
```

```
parça                      ifade deyim  değeri
2 + 3                       True  True  5
toplam                      True  True  3
len('olcum')                True  True  5
[k for k in (1, 2, 3)]      True  True  [1, 2, 3]
toplam if toplam else 0     True  True  3
toplam = 5                 False  True  —
toplam += 1                False  True  —
if toplam: pass            False  True  —
for k in (1, 2): pass      False  True  —
pass                       False  True  —

10 parçadan ifade 5, deyim 10; yalnız deyim olan 5
```

On parçanın **5**'i ifade, **10**'u deyim. Deyim sütununun tamamen dolu olması ayrımın
yönünü verir: **her ifade aynı zamanda bir deyimdir** — tek başına bir satıra konduğunda
değeri hesaplanır ve atılır. Tersi geçerli değildir; yalnız deyim olan **5** parça bir
ifadenin durabileceği hiçbir yere konamaz.

Ayrımın kurs için doğrudan bir sonucu var. Önceki dersin ölçtüğü on bir biçimin hepsi bir
nesneyi **tüketiyordu** ve tükettikleri şey bir ifadenin değeriydi; özel yöntemler ifadeler
üzerinde çalışır. Tabloda `toplam += 1` satırının ifade çıkmaması da bu yüzden önemlidir:
Programlama Temelleri kursu bileşik atamanın bir deyim olduğunu söylemişti, burada ölçülen
şey o deyimin **hangi yöntemi çağırdığı** ve dönen değeri ne yaptığıdır. `__iadd__`
çağrısının sonucu ortada bir değer olarak durmaz, doğrudan ada geri bağlanır — ayrıntısı
işleçler dersinde ödenecek.

Son bir gözlem tabloda saklıdır: `if toplam: pass` tek satırda yazıldığı hâlde geçerli bir
deyimdir ve hiç girinti kullanmaz. Girinti bir bloğu yazmanın **tek** yolu değildir; bloğu
zorunlu kılan şey deyimin kendisidir, girinti ise birden çok deyimli bir gövdeyi yazmanın
yoludur.

#### Özet

- Girinti atılmaz; satır başındaki boşluk `INDENT` ve `DEDENT` birimlerine çevrilir ve blok
  yapısını bu birimler kurar.
- Yalnız girintisiyle ayrılan iki kaynak aynı **21** içerik birimini aynı sırada taşır;
  tek bir `DEDENT` biriminin yeri ağacı ve sonucu değiştirir.
- Girintinin genişliği serbesttir — dört ayrı genişlik tek bir ağaç verir — ama bir blok
  içinde tutarlı olmak zorundadır.
- Tutarsız girinti derleme aşamasında durdurur; `TabError` bir `IndentationError`, o da bir
  `SyntaxError`'dır.
- Girinti yalnız mantıksal satır başında dilbilgiseldir: açık ayracın içindeki **3** fiziksel
  satır tek bir `NEWLINE` üretir ve `INDENT` sayısı **0** kalır.
- On parçadan **5**'i ifade, **10**'u deyimdir: her ifade bir deyim olarak yazılabilir,
  hiçbir deyim ifadenin yerine konamaz.

#### Sonraki Adım

Bu derste en sık yazılan deyim olan atama, tabloda yalnızca "ifade değil" olduğu için
göründü; ne yaptığına bakılmadı. Programlama Temelleri kursu atamanın bir bağlama olduğunu
kurmuştu — ad bir kutu değil, bir nesneye takılan etikettir. Sonraki ders bu bağı nesne
tarafından ölçer: iki ad aynı nesneye bağlı olduğunda bunu ne söyler, eşit iki değerin aynı
nesne çıkması hangi koşulda gerçekleşir ve bir tipin "değiştirilemez" olması bağlamayı nasıl
etkiler?

### Değişkenler ve Veri Tipleri

Source: https://academia.sh/tr/kurslar/python-temelleri/degiskenler-ve-veri-tipleri

Ad tip taşımaz, nesne taşır: tek bir ad yedi ayrı tipe bildirimsiz bağlanır, `+=` yazımı dört tipin üçünde adı yeniden bağlayıp birinde nesneyi değiştirir ve eşit iki değerin aynı nesne çıkması gerçekleştirime bağlıdır.

Önceki ders girintinin blokları, blokların da deyimleri taşıdığını gösterdi ve en sık
yazılan deyim olan atama tabloda yalnızca "ifade değil" olduğu için göründü. Bu ders o
deyimin ne yaptığına bakar.

Programlama Temelleri kursu atamanın bir **bağlama** olduğunu, değişkenin ya adlandırılmış
bir bellek gözü ya da nesneye takılmış bir etiket olarak modellenebileceğini, `is` ile `==`
arasındaki ayrımı ve takma adı zaten kurmuştu. **Bunlar tekrarlanmaz.** Orada kurulan şey
bir kavramdı: bir dil iki modelden birini seçer. Burada ölçülen şey Python'un seçtiği
modelin **gözlenebilir sonuçlarıdır** — adın hiç tip taşımaması, `+=` yazımının tipe göre
bağ mı kurduğu yoksa nesneyi mi değiştirdiği ve eşit iki değerin aynı nesne çıkıp
çıkmamasının neye dayandığı.

#### Ad Tip Taşımaz

Python'da her değer bir **nesnedir** ve tip nesnenin özelliğidir. Ad bir bildirim değil,
yalnızca bir bağdır; ne bir tip ilan eder ne de bir tipe kilitlenir. `type` yerleşiği bu
yüzden nesneye sorulur, ada değil.

- **DT15** — Yedi ayrı tipten birer değer tek bir ada sırayla bağlanır; kaynakta hiçbir tip
  bildirimi yoktur. Ölçülen şey her bağlamadan sonra nesnenin bildirdiği tip adıdır.
- **DT16** — Son bağlama bir işlevedir; işlevin de bağlanabilir bir nesne olduğu böylece
  aynı tabloda görünür.

```python
"""Ad bir etikettir: tipi nesne tasir, ad tasimaz."""

DEGERLER = (12, 3.5, "olcum", (1, 2), [1, 2], {"kuzey": 1}, None)


def uzunluk(metin):
    return len(metin)


olcum = None
tipler = []
for deger in DEGERLER:
    olcum = deger                      # tek ad, sirayla yedi nesne
    tipler.append(type(olcum).__name__)

print("tek ada sırayla bağlanan tipler:", " ".join(tipler))
print("bağlama sayısı", len(DEGERLER), "— hiçbirinde tip bildirimi yok")
print("son bağlamadan sonra adın tipi:", type(olcum).__name__)
olcum = uzunluk                        # ayni ad, simdi bir islev
print("aynı ad bir işleve de bağlanabilir:",
      type(olcum).__name__, "->", olcum("olcum"))
```

```
tek ada sırayla bağlanan tipler: int float str tuple list dict NoneType
bağlama sayısı 7 — hiçbirinde tip bildirimi yok
son bağlamadan sonra adın tipi: NoneType
aynı ad bir işleve de bağlanabilir: function -> 5
```

Tek bir ad **7** ayrı tipe bağlandı ve sekizincide bir işleve. Hiçbir bağlamada bir bildirim
yazılmadı, çünkü yazılacak bir yer yok: `olcum` adı bir kap değil, üzerine takıldığı nesneye
işaret eden bir addır. Adın tipini soran bir işlem de yoktur; `type` çağrısı adın **bağlı
olduğu nesneye** gider ve yanıtı o nesne verir.

Son satır bir sınıfı daha kapatıyor. İşlev de bir nesnedir; tanımlanması, bir ada bir nesne
bağlamaktan başka bir şey değildir. Bu yüzden bir işlev bir ada, bir listeye ya da bir
sözlüğe konabilir ve oradan çağrılabilir.

Python'da bir bildirime **benzeyen** bir yazım vardır ve karıştırılmaya elverişlidir: bir
parametrenin ya da dönüş değerinin yanına bir tip adı yazılabilir. Bu yazım bir bildirim
değil, bir **nottur**; yorumlayıcı onu saklar ama zorlamaz.

```python
"""Not bildirim degildir: yorumlayici yazilani zorlamaz."""


def iki_kati(n: int) -> int:
    return n * 2


print("tam sayı:", iki_kati(12))
print("dizgi:   ", repr(iki_kati("ol")))
print("liste:   ", iki_kati([1, 2]))
print("notların saklandığı yer:", list(iki_kati.__annotations__))
```

```
tam sayı: 24
dizgi:    'olol'
liste:    [1, 2, 1, 2]
notların saklandığı yer: ['n', 'return']
```

İşlev bir tam sayı beklediğini yazdığı hâlde dizgiyle de listeyle de çalıştı ve hiçbir uyarı
üretmedi. Yazılan notlar kaybolmuş da değil; işlevin üzerinde iki ad altında duruyorlar.
Yani not, yürütmeye değil **okuyucuya ve dışarıdan çalışan denetleyicilere** yazılır. Nota
bakıp "burada tip denetleniyor" sanmak, dilin en pahalı yanlış okumalarından biridir.

Buna **dinamik tipleme** denir ve doğru okunması gerekir: tipin olmadığı anlamına gelmez,
tip denetiminin **yürütme aşamasına** ertelendiği anlamına gelir. Bu, ilk dersteki sınırın
aynısıdır. Bir adın var olup olmadığı derlenirken bilinemiyordu; bir nesnenin bir işlemi
karşılayıp karşılamadığı da bilinemez, çünkü nesne ancak çalışırken ortaya çıkar. Kusur
bulunduğunda ortaya çıkan istisna da ilk derste ölçülenle aynıdır — aranan özel yöntem yoksa
`TypeError`.

#### Yerleşik Tipler ve Taşıdıkları Protokol

Tip nesnenindir; öyleyse tipleri birbirinden ayıran şey nedir? Programlama Temelleri kursu
temel veri tiplerini bir sınıflandırma olarak kurmuştu — sayı, metin, mantıksal değer,
koleksiyon. **O sınıflandırma tekrarlanmaz.** Burada sorulan şey başkadır ve ilk dersin
ölçüsünden doğar: bir yerleşik tip, hangi sözdizim biçimlerini karşılayabildiğini
**tanımladığı özel yöntemlerle** ilan eder. Tipin adı hiçbir şey söylemez; yöntem listesi
her şeyi söyler.

- **DT17** — Her tip tek bir örnekle temsil edilir ve yoklama örneğin kendisine değil,
  **tipine** yapılır; sorulan şey tipin o yöntemi taşıyıp taşımadığıdır.
- **DT18** — "Değişebilir" sütunu tipin bilinen bir niteliğidir, ölçülmez; tabloda yalnız
  yöntem sütunları ölçümdür.

```python
"""Yerlesik tipler ne yapabiliyor: tasidiklari ozel yontemlere bakilir."""

ORNEKLER = (("int", 12, False), ("float", 3.5, False), ("bool", True, False),
            ("str", "olcum", False), ("tuple", (1, 2), False),
            ("list", [1, 2], True), ("dict", {"kuzey": 1}, True),
            ("set", {1, 2}, True), ("NoneType", None, False))
PROTOKOL = ("__len__", "__iter__", "__contains__", "__getitem__", "__add__")

print(f"{'tip':<9s} {'değişebilir':>11s}  "
      + " ".join(f"{p.strip('_'):>10s}" for p in PROTOKOL))
for ad, ornek, degisebilir in ORNEKLER:
    tasidigi = " ".join(f"{'var' if hasattr(type(ornek), p) else '—':>10s}"
                        for p in PROTOKOL)
    print(f"{ad:<9s} {str(degisebilir):>11s}  {tasidigi}")

print()
print("bool bir int mi:", isinstance(True, int), "| True + True =", True + True)
buyuk = 2 ** 100
print("tam sayının genişliği sabit değil:", buyuk.bit_length(), "bit;",
      "karesi", (buyuk * buyuk).bit_length(), "bit")
```

```
tip       değişebilir         len       iter   contains    getitem        add
int             False           —          —          —          —        var
float           False           —          —          —          —        var
bool            False           —          —          —          —        var
str             False         var        var        var        var        var
tuple           False         var        var        var        var        var
list             True         var        var        var        var        var
dict             True         var        var        var        var          —
set              True         var        var        var          —          —
NoneType        False           —          —          —          —          —

bool bir int mi: True | True + True = 2
tam sayının genişliği sabit değil: 101 bit; karesi 201 bit
```

Tablo tipleri adlarına göre değil, yeteneklerine göre öbekliyor. Üç sayısal tip yalnız
`__add__` taşıyor: toplanabiliyorlar ama üzerlerinde döngü kurulamıyor, uzunlukları
sorulamıyor. `str`, `tuple` ve `list` beş sütunun beşini de dolduruyor; aralarındaki tek
fark ölçülen sütunlarda değil, değişebilirlik sütunundadır. `dict` ve `set` üstteki üç
protokolü taşıyıp `__add__` taşımıyor — iki sözlüğün `+` ile birleştirilememesinin nedeni
bir yasak değil, o yöntemin **tanımlı olmamasıdır**. `NoneType` satırı ise boş: `None`
üzerinde bu biçimlerin hiçbiri çalışmaz ve ilk dersteki çıplak nesneyle aynı sonucu verir.

`set` satırındaki tek boşluk da öğreticidir. Küme `__contains__` taşıyor ama `__getitem__`
taşımıyor: içinde bir öğenin bulunup bulunmadığı sorulabilir, sırayla kaçıncı olduğu
sorulamaz. Bu, kümenin "sırasız" olmasının kod düzeyindeki karşılığıdır.

Alt iki satır iki Python'a özgü noktayı kapatıyor. `bool`, `int`'in bir alt tipidir — bu
yüzden `True + True` bir `TypeError` değil, **2** verir. Ve tam sayının sabit bir genişliği
yoktur: yüz bitlik bir değer **101** bitte durur, karesi **201** bite büyür. Bilgisayarlar
Nasıl Çalışır kursunda sabit genişlikli tipin taşma davranışı kurulmuştu; burada ölçülen
fark, Python'un tam sayısında böyle bir sınırın **bulunmamasıdır**. Sığmama diye bir durum
yoktur, yalnızca büyüyen bir gösterim vardır.

#### Bağın Kimliği

Aynı nesneye birden çok ad bağlanabilir ve `is` işleci bunu sorar. Bu ders **kimlik
sayısını basmaz**: sayı ortama bağlıdır, koşumdan koşuma değişir ve hiçbir programın
doğruluğu ona dayanmamalıdır. Sorulacak soru "hangi nesne" değil, "aynı nesne mi"dir ve
buna yalnız `is` yanıt verir.

Sorunun Python'a özgü karşılığı bileşik atamada görünür. `x += ek` tek bir yazımdır, ama iki
ayrı sonucu vardır: ya adı yeni bir nesneye bağlar, ya da bağlı olduğu nesneyi yerinde
değiştirir. Hangisinin olduğu nesneye ikinci bir ad takılarak ölçülebilir.

- **DT19** — Her ölçümde değer bir ada bağlanır, aynı nesneye ikinci bir ad takılır ve
  `+=` yalnız birinci ad üzerinde çalıştırılır. `is` ile birinci adın hâlâ aynı nesnede olup
  olmadığına bakılır.
- **DT20** — Dört tip, üçü değiştirilemez biri değiştirilebilir olacak biçimde
  seçilmiştir; sonuç sütunu tipin bu niteliğini yansıtır, seçilen değerleri değil.

```python
"""Ayni yazim, iki ayri sonuc: bag mi yeniden kuruldu, nesne mi degisti?"""


def yerinde_mi(deger, ek):
    x = deger
    izci = x                     # ayni nesneye takilan ikinci ad
    x += ek
    return x is izci, izci


print(f"{'tip':<9s} {'+= sonrası':<22s} ikinci adın gördüğü")
for ad, deger, ek in (("tam sayı", 10, 1),
                      ("dizgi", "ol", "cum"),
                      ("demet", (1, 2), (3,)),
                      ("liste", [1, 2], [3])):
    ayni, izci = yerinde_mi(deger, ek)
    print(f"{ad:<9s} {'nesne değişti' if ayni else 'ad yeniden bağlandı':<22s} {izci!r}")
```

```
tip       += sonrası             ikinci adın gördüğü
tam sayı  ad yeniden bağlandı    10
dizgi     ad yeniden bağlandı    'ol'
demet     ad yeniden bağlandı    (1, 2)
liste     nesne değişti          [1, 2, 3]
```

Dört tipin **üçünde** ad yeniden bağlandı, **birinde** nesne değişti. Ayrımı yaratan şey
tipin **değiştirilebilir** olup olmamasıdır. Tam sayı, dizgi ve demet **değiştirilemez
(immutable)** tiplerdir: bir kez kurulan nesnenin içeriği hiçbir yolla değiştirilemez.
Böyle bir nesnede `+=` yerinde bir işlem yapamaz; yeni bir nesne üretip **adı** ona bağlar
ve ikinci ad eski nesnede kalır. Sağdaki sütun bunu doğrudan gösteriyor — üç satırda ikinci
ad hâlâ başlangıç değerini görüyor.

Listede sonuç tersine döner. Nesnenin kendisi büyür; ada dokunulmaz, çünkü dokunmaya gerek
yoktur. İkinci ad da aynı nesneye bağlı olduğundan büyümüş hâli görür. Aynı satırın iki tipte
iki ayrı anlama gelmesi bir tutarsızlık değil, tek bir kuralın iki sonucudur: **değiştirilemez
bir nesne üzerinde yerinde işlem tanımlanamaz.**

Bu, kursun ikinci iddiasının ilk belirtisidir. `x = x + ek` ile `x += ek` yazımları burada
aynı sonucu vermez ve fark tesadüfi değildir; iki yazım **iki ayrı özel yöntemi** çağırır.
Adları sonraki derste konacak.

#### Önbelleklenmiş Nesne

Geriye tek bir soru kalıyor: eşit iki değer aynı nesne midir? Yanıt, sorunun sanıldığından
çok daha az güvence taşıdığıdır.

- **DT21** — Karşılaştırılan değerler bir işlev çağrısı içinde, **çalışma sırasında**
  üretilir; böylece kaynakta yan yana duran iki sabitin derleme aşamasında birleştirilmesi
  ölçüme karışmaz.
- **DT22** — "Güvence" sütunu, sonucun neye dayandığını söyler: `dil tanımı` satırı her
  yorumlayıcıda aynıdır, `gerçekleştirim` satırı **değişebilir**.
- **DT23** — Kimlik yalnız `is` ile sorulur; hiçbir kimlik sayısı basılmaz.

```python
"""Onbelleklenmis nesne: sonucu GERCEKLESTIRIME BAGLIDIR."""


def uret(a, b):
    return a + b                 # deger calisma sirasinda uretilir


def bosluk():
    return None


def dogru():
    return 1 == 1


CIFTLER = (
    ("küçük tam sayı", uret(2, 3), uret(1, 4)),
    ("büyük tam sayı", uret(500, 1), uret(499, 2)),
    ("kısa dizgi", uret("ol", "cum"), uret("olc", "um")),
    ("liste", uret([1], [2]), uret([1], [2])),
)

print(f"{'çift':<16s} {'==':>6s} {'is':>6s}   güvence")
for ad, x, y in CIFTLER:
    guvence = "dil tanımı" if isinstance(x, list) else "gerçekleştirim"
    print(f"{ad:<16s} {str(x == y):>6s} {str(x is y):>6s}   {guvence}")

print("\ndil tanımının güvence verdiği tekil nesneler:")
print("  başka yerden dönen None aynı nesne mi:", bosluk() is None)
print("  başka yerden dönen doğruluk değeri aynı nesne mi:", dogru() is True)
```

```
çift                 ==     is   güvence
küçük tam sayı     True   True   gerçekleştirim
büyük tam sayı     True  False   gerçekleştirim
kısa dizgi         True  False   gerçekleştirim
liste              True  False   dil tanımı

dil tanımının güvence verdiği tekil nesneler:
  başka yerden dönen None aynı nesne mi: True
  başka yerden dönen doğruluk değeri aynı nesne mi: True
```

Dört çiftin dördünde de `==` **True** veriyor: değerler eşit. `is` sütunu ise dağılıyor.
Küçük tam sayıda iki ayrı hesap aynı nesneyi verdi; büyük tam sayıda ve kısa dizgide
vermedi.

Bu üç satır **gerçekleştirime bağlıdır ve böyle okunmalıdır.** Bir yorumlayıcı, sık
kullanılan bazı değiştirilemez nesneleri önceden kurup her istendiğinde aynısını verebilir;
buna **önbelleklenmiş nesne (interning)** denir. Hangi değerlerin bu kapsama girdiği dilin
tanımında yazmaz. Başka bir yorumlayıcıda — hatta aynı yorumlayıcının başka bir koşumunda —
bu üç satırın üçünün de `True` ya da üçünün de `False` çıkması dilin tanımını bozmaz. Tablo
bir davranışı **açıklar**, bir kural **kurmaz**.

Dördüncü satır ayrı bir sınıftandır ve güvencesi vardır: bir liste yazımı her
değerlendirildiğinde **yeni bir nesne** kurar. Önbellekleme burada söz konusu bile değildir,
çünkü nesne değiştirilebilirdir — paylaşılsaydı birinde yapılan değişiklik ötekinde
görünürdü.

Buradan tek bir pratik kural çıkar ve istisnası yoktur: **eşitlik `==` ile sorulur, `is`
yalnız kimlik sorusu içindir.** İki değerin aynı olup olmadığını `is` ile sınayan bir kod,
küçük değerlerde çalışıp büyüklerde sessizce yanlış yanıt verir — hata iletisi vermeyen, en
zor bulunan kusur sınıfıdır.

Son iki satır kuralın meşru kullanımını gösteriyor. `None` dil tanımı gereği tek bir
nesnedir; nereden gelirse gelsin aynı nesnedir. Doğruluk değerleri için de aynısı geçerlidir.
Bu yüzden bir değerin boş olup olmadığı `== None` ile değil, **`is None`** ile sorulur:
burada gerçekten kimlik sorulmaktadır ve yanıtı dil güvence eder.

#### Özet

- Tip nesnenin özelliğidir, adın değil; tek bir ad **7** ayrı tipe ve bir işleve hiçbir
  bildirim olmadan bağlanabilir.
- Dinamik tipleme tipin yokluğu değil, tip denetiminin yürütme aşamasına ertelenmesidir;
  karşılıksız bir işlem `TypeError` ile düşer.
- `+=` yazımı dört tipin **3**'ünde adı yeni bir nesneye bağlar, **1**'inde nesneyi yerinde
  değiştirir; ayrımı yapan şey tipin değiştirilebilir olup olmamasıdır.
- Değiştirilemez bir nesnede yerinde işlem tanımlanamaz; bu yüzden ikinci bir ad eski değeri
  görmeye devam eder.
- Eşit iki değerin aynı nesne çıkması gerçekleştirime bağlıdır ve programın doğruluğu buna
  dayandırılamaz; liste yazımının her seferinde yeni nesne kurması ise dil tanımının
  güvencesidir.
- Eşitlik `==` ile sorulur; `is` yalnız kimlik içindir — `None` sınaması bu yüzden `is None`
  ile yazılır.

#### Sonraki Adım

Bu derste `x += ek` yazımının tipe göre iki ayrı iş yaptığı ölçüldü ama yaptığı işin **adı**
konmadı. Sonraki ders o adı koyar ve kursun ikinci iddiasını öder: `n + m` ile `n += m` aynı
işlecin iki yazımı değil, iki ayrı özel yöntemin çağrısıdır. Aritmetik, karşılaştırma,
mantıksal ve üyelik işleçlerinin her biri hangi yöntemi çağırıyor, üyelik sınaması aradığı
yöntemi bulamadığında nereye düşüyor ve bir işleç hiçbir karşılık bulamadığında ne oluyor?

### İşleçler

Source: https://academia.sh/tr/kurslar/python-temelleri/islecler

On bir işleç biçimi 10 ayrı özel yöntemi çağırır ve biri karşılıksız kalır: `n + m` `__add__`, `n += m` `__iadd__` çağırır; `x in n` `__contains__` bulamazsa yineleme protokolüne düşer ve tek çağrı yerine beş çağrı ödenir.

Önceki ders `x += ek` yazımının tipe göre iki ayrı iş yaptığını ölçtü: üç tipte adı yeni bir
nesneye bağladı, birinde nesneyi yerinde değiştirdi. Yaptığı işin adı konmadı. Bu ders o adı
koyar.

Programlama Temelleri kursu işleçleri öncelik, birleşme yönü ve kısa devre değerlendirme
başlıklarıyla kurmuş, aynı işlecin farklı tiplerde farklı anlam taşıyabildiğini de
göstermişti. **Bunlar tekrarlanmaz.** Orada kurulan şey kavramdı — bir işlecin anlamı
işlenenin tipine bağlıdır. Burada ölçülen şey o bağın **düzeneğidir**: hangi işleç hangi
adı arıyor, aradığını bulamazsa nereye düşüyor ve hiçbir yerde bulamazsa ne oluyor.

#### İşleç Bir Yöntem Çağrısıdır

İlk dersin kurgusu bu ders için genişletilir. `Izci` var olan yöntemlerini korur; üzerine
çıkarma, çarpma, sağdan çarpma, küçüklük, işaret değiştirme ve kesişim eklenir. Her yöntem
çağrıldığında yine adını kaydeder.

- **DT24** — Ölçülen on bir biçimin tamamı iki `Izci` nesnesi ya da bir `Izci` ile bir tam
  sayı üzerinde çalışır; ölçüm işlecin **sonucuna** değil, çağırdığı yöntem adına bakar.
- **DT25** — Son biçim (`n / m`) bilerek karşılıksız bırakılmıştır: `Izci` bölme için
  hiçbir yöntem tanımlamaz. O satırdaki ünlem önekli kayıt, bir çağrıyı değil bir istisnayı
  gösterir.

```python
"""Isleclerin cagirdigi ozel yontemler; Izci her katilimini kaydeder."""

KAYIT = []


def kaydet(ad):
    KAYIT.append(ad)


class Izci:
    """Ortak kurgunun islec bolumu; bu derste alti yontem eklenmistir."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __iter__(self):
        kaydet("__iter__")
        self._i = 0
        return self

    def __next__(self):
        kaydet("__next__")
        if self._i >= len(self.ogeler):
            raise StopIteration
        deger = self.ogeler[self._i]
        self._i += 1
        return deger

    def __contains__(self, x):
        kaydet("__contains__")
        return x in self.ogeler

    def __add__(self, o):
        kaydet("__add__")
        return Izci(self.ogeler + list(o.ogeler))

    def __iadd__(self, o):
        kaydet("__iadd__")
        self.ogeler.extend(o.ogeler)
        return self

    def __eq__(self, o):
        kaydet("__eq__")
        return isinstance(o, Izci) and self.ogeler == o.ogeler

    def __sub__(self, o):                      # eklendi
        kaydet("__sub__")
        return Izci([x for x in self.ogeler if x not in o.ogeler])

    def __mul__(self, k):                      # eklendi
        kaydet("__mul__")
        return Izci(self.ogeler * k)

    def __rmul__(self, k):                     # eklendi
        kaydet("__rmul__")
        return Izci(self.ogeler * k)

    def __lt__(self, o):                       # eklendi
        kaydet("__lt__")
        return len(self.ogeler) < len(o.ogeler)

    def __neg__(self):                         # eklendi
        kaydet("__neg__")
        return Izci([-x for x in self.ogeler])

    def __and__(self, o):                      # eklendi
        kaydet("__and__")
        return Izci([x for x in self.ogeler if x in o.ogeler])


def olc(islev):
    """Bir islec bicimini calistirir ve cagrilan protokolleri dondurur."""
    KAYIT.clear()
    try:
        islev()
    except Exception as e:
        KAYIT.append(f"!{type(e).__name__}")
    return list(KAYIT)


def yerinde_toplama():
    a = Izci()
    a += Izci()


BICIMLER = (
    ("n + m", lambda: Izci() + Izci()),
    ("n += m", yerinde_toplama),
    ("n - m", lambda: Izci() - Izci()),
    ("n * 2", lambda: Izci() * 2),
    ("2 * n", lambda: 2 * Izci()),
    ("-n", lambda: -Izci()),
    ("n < m", lambda: Izci() < Izci()),
    ("n == m", lambda: Izci() == Izci()),
    ("n & m", lambda: Izci() & Izci()),
    ("x in n", lambda: 9 in Izci()),
    ("n / m", lambda: Izci() / Izci()),
)

print(f"{'işleç biçimi':<14s} {'çağrı':>5s}  protokol")
for ad, islev in BICIMLER:
    c = olc(islev)
    print(f"{ad:<14s} {len(c):5d}  {' '.join(c)}")

kayitlar = [olc(f) for _, f in BICIMLER]
tekil = sorted({p for c in kayitlar for p in c if not p.startswith("!")})
print(f"\n{len(BICIMLER)} biçim, ayrı protokol {len(tekil)}, "
      f"toplam çağrı {sum(len(c) for c in kayitlar)}, "
      f"karşılıksız biçim {sum(1 for c in kayitlar if any(p.startswith('!') for p in c))}")
```

```
işleç biçimi   çağrı  protokol
n + m              1  __add__
n += m             1  __iadd__
n - m              1  __sub__
n * 2              1  __mul__
2 * n              1  __rmul__
-n                 1  __neg__
n < m              1  __lt__
n == m             1  __eq__
n & m              1  __and__
x in n             1  __contains__
n / m              1  !TypeError

11 biçim, ayrı protokol 10, toplam çağrı 11, karşılıksız biçim 1
```

On bir biçim **10 ayrı protokol** çağırıyor ve **1** biçim karşılıksız kalıyor. Tablonun
okunuşu tektir: solda yazılan simge, sağdaki adın kısaltmasıdır. `-` işleci `__sub__`,
`*` işleci `__mul__`, `<` işleci `__lt__` demektir. `&` satırı bu okumanın en açık örneğidir
— işlecin ne yaptığı tipe kalmıştır: tam sayılarda bit düzeyinde birleştirme yapar, burada
`Izci` onu kesişim olarak tanımlamıştır. İşleç bir anlam taşımaz, bir **ad** taşır.

Son satır bunun öteki yüzü. `Izci` bölme için hiçbir yöntem tanımlamadığından `n / m` biçimi
`TypeError` ile düşüyor. Kaynak kusursuzdur ve derleme aşamasını geçmiştir; kusur, çağrılacak
adın bulunamamasıdır. Bir işlecin "desteklenmemesi" diye ayrı bir durum yoktur — yalnız
tanımlanmamış bir yöntem vardır. Bir tipe bölme kazandırmanın yolu da bu yüzden tektir:
`__truediv__` adında bir yöntem yazmak.

#### Toplama ile Yerinde Toplama

Tablonun ilk iki satırı kursun ikinci iddiasını ödüyor. `n + m` **`__add__`**, `n += m`
**`__iadd__`** çağırıyor. İkisi birbirinin kısaltması değildir; ayrı adlar, ayrı
sözleşmelerdir.

`__add__` işlenenlerine dokunmaz, **yeni bir nesne** üretip döndürür. `__iadd__` var olan
nesneyi **yerinde değiştirip kendisini** döndürür. Önceki dersin ölçtüğü ayrım budur: bir
tip `__iadd__` tanımlamışsa `+=` nesneyi değiştirir ve ona bağlı ikinci ad da değişikliği
görür; tanımlamamışsa `+=` yazımı `__add__`'e düşer, yeni nesne üretilir ve **yalnız soldaki
ad** ona bağlanır. Tam sayı, dizgi ve demetin geçen derste "adı yeniden bağladı" çıkmasının
nedeni tam olarak budur — bu tipler değiştirilemez olduğu için `__iadd__` tanımlayamazlar.

Buradan pratik bir sonuç çıkar: `a += b` yazımı, `a` başkalarıyla paylaşılan bir nesneye
bağlıysa **onların gördüğü değeri de değiştirir**. `a = a + b` yazımı ise asla değiştirmez.
İki satır aynı işi yapmaz ve aralarındaki seçim üslup değildir.

#### Sağdaki İşlenene Düşme

`n * 2` ile `2 * n` satırları ayrı yöntemler çağırıyor: `__mul__` ve `__rmul__`. Nedeni
şudur: `2 * n` yazıldığında önce **soldaki** işlenenin tipine sorulur. Tam sayı, bir `Izci`
ile nasıl çarpılacağını bilmez ve "bilmiyorum" yanıtı verir. Bunun üzerine **sağdaki**
işlenene, işlemin ters yönü sorulur — aranan ad `__mul__` değil, başında `r` olan
`__rmul__`'dır.

Bu düşüş, dilin genişletilebilirliğinin temelidir. Yerleşik bir tipe yeni bir davranış
eklenemez; ama yeni bir tip, yerleşik tiplerin solda durduğu ifadelere `__radd__`, `__rmul__`
gibi yöntemlerle katılabilir. Aranan yöntem iki işlenende de yoksa `n / m` satırındaki sonuç
alınır.

#### Önceliğin Belirlediği Çağrı Sırası

Bir ifadede birden çok işleç varsa hangisinin önce çağrıldığını **öncelik** belirler.
Programlama Temelleri kursu öncelik ve birleşme yönü tablosunu kurmuştu; burada ölçülen şey
o tablonun kod düzeyindeki karşılığıdır. Öncelik bir okuma kolaylığı değildir: ikinci dersin
gösterdiği ağacı biçimlendirir ve ağaç da **yöntemlerin çağrılma sırasını** verir.

- **DT26** — Üç ifade aynı üç nesne üzerinde kurulur ve yalnız işleçleri ile parantezleriyle
  ayrılır; ölçülen şey kayıt dizisinin **sırasıdır**, uzunluğu değil.

```python
def carpim_once():
    Izci((1,)) + Izci((2,)) * 2


def toplama_once():
    (Izci((1,)) + Izci((2,))) * 2


def karsilastirma_sonra():
    Izci((1,)) + Izci((2,)) == Izci((1, 2))


print(f"{'ifade':<16s} çağrı sırası")
for ad, islev in (("n + m * 2", carpim_once),
                  ("(n + m) * 2", toplama_once),
                  ("n + m == k", karsilastirma_sonra)):
    print(f"{ad:<16s} {' '.join(olc(islev))}")
```

```
ifade            çağrı sırası
n + m * 2        __mul__ __add__
(n + m) * 2      __add__ __mul__
n + m == k       __add__ __eq__
```

İlk iki satırda aynı iki yöntem çağrılıyor, sıraları ters. Çarpma toplamadan önce geldiği
için `__mul__` önce çalışıyor ve ürettiği nesne `__add__`'e işlenen olarak giriyor;
parantez konduğunda sıra tersine dönüyor. Üçüncü satır aritmetiğin karşılaştırmadan önce
geldiğini gösteriyor: toplama tamamlanmadan eşitlik sorulamaz, çünkü karşılaştırılacak
nesne henüz yoktur. Öncelik kuralları, hangi yöntemin hangi nesneyi işlenen olarak
göreceğini belirler.

#### Üyelik Sınaması Yöntemini Bulamazsa

İkinci iddianın en görünür örneği üyelik sınamasıdır. `x in n` biçimi önce `__contains__`
arar; bulamazsa **yineleme protokolüne düşer** ve nesneyi baştan sona tarayarak öğeleri
tek tek karşılaştırır. Sözdizim aynıdır, bedel değildir.

- **DT27** — Karşılaştırılan iki nesne aynı üç öğeyi taşır; tek fark `Izci`'nin
  `__contains__` tanımlaması, `Yinelenir`'in tanımlamamasıdır.
- **DT28** — Her sınama bir kez bulunan ve bir kez bulunmayan bir değerle çalıştırılır;
  ölçülen şey sonuç ile çağrı sayısıdır.

```python
class Yinelenir:
    """__contains__ tanimlamaz; yalniz yineleme protokolunu tasir."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __iter__(self):
        kaydet("__iter__")
        self._i = 0
        return self

    def __next__(self):
        kaydet("__next__")
        if self._i >= len(self.ogeler):
            raise StopIteration
        deger = self.ogeler[self._i]
        self._i += 1
        return deger


UYELIK = (
    ("2 in Izci()      ", lambda: 2 in Izci()),
    ("9 in Izci()      ", lambda: 9 in Izci()),
    ("2 in Yinelenir() ", lambda: 2 in Yinelenir()),
    ("9 in Yinelenir() ", lambda: 9 in Yinelenir()),
)

print(f"{'üyelik sınaması':<18s} {'sonuç':>5s} {'çağrı':>5s}  protokol")
for ad, islev in UYELIK:
    KAYIT.clear()
    sonuc = islev()
    c = list(KAYIT)
    print(f"{ad:<18s} {str(sonuc):>5s} {len(c):5d}  {' '.join(c)}")
```

```
üyelik sınaması    sonuç çağrı  protokol
2 in Izci()         True     1  __contains__
9 in Izci()        False     1  __contains__
2 in Yinelenir()    True     3  __iter__ __next__ __next__
9 in Yinelenir()   False     5  __iter__ __next__ __next__ __next__ __next__
```

`Izci` iki durumda da **1** çağrı ödüyor: sorulan soru doğrudan nesneye gidiyor ve nesne
yanıtı kendi biliyor. `Yinelenir` aynı sonuçları veriyor ama bedeli ayrı — bulunan değerde
**3**, bulunmayan değerde **5** çağrı.

Sayıların dağılımı düşüşün nasıl işlediğini de gösteriyor. Bulunan değerde tarama ikinci
öğede duruyor: bir `__iter__` ve iki `__next__`. Bulunmayan değerde ise üç öğenin üçü de
okunuyor ve dördüncü `__next__` `StopIteration` fırlatarak taramayı bitiriyor — ilk dersteki
"öğe sayısından bir fazla" örüntüsünün aynısı. **Bulunmayan bir değer her zaman tam tarama
ödettirir.**

`Izci`'nin `__iter__` de tanımlaması bir noktayı daha kapatıyor: iki yöntem de tanımlıysa
`in` biçimi **`__contains__`'i seçer**, çünkü düşüş ancak arananın yokluğunda olur. Sonuç
ikisinde de aynı çıkabilir; ödenen çağrı sayısı çıkmaz.

#### Zincirlenen Karşılaştırma ve Mantıksal İşleçler

İki yazım kaldı ve ikisi de tabloya sığmıyor, çünkü çağırdıkları yöntem sayısı sabit değil.

- **DT29** — Ortadaki işlenen bir işlev çağrısıyla üretilir ve bu işlev de kayda katılır;
  böylece işlenenin **kaç kez** üretildiği sayılabilir.
- **DT30** — `Izci` doğruluk sınaması için hiçbir yöntem tanımlamaz; mantıksal satırlarda
  kaydın boş çıkması bu yüzden anlamlıdır. Sınamanın hangi yöntemi çağırdığı akış konusunun
  ilk dersinde ölçülür.

```python
def orta():
    kaydet("orta()")                 # ortadaki islenen kac kez uretildi?
    return Izci((1, 2))


def zincir_iki_dogru():
    Izci((1,)) < orta() < Izci((1, 2, 3))


def acik_yazim():
    Izci((1,)) < orta() and orta() < Izci((1, 2, 3))


def zincir_ilk_yanlis():
    Izci((1, 2, 3)) < orta() < Izci((1, 2, 3, 4))


print(f"{'yazım':<32s} {'çağrı':>5s}  protokol")
for ad, islev in (("a < orta() < c  (ikisi de doğru)", zincir_iki_dogru),
                  ("a < orta() and orta() < c", acik_yazim),
                  ("a < orta() < c  (ilki yanlış)", zincir_ilk_yanlis)):
    c = olc(islev)
    print(f"{ad:<32s} {len(c):5d}  {' '.join(c)}")

print()
print(f"{'mantıksal yazım':<12s} {'çağrı':>5s}  protokol")
for ad, islev in (("n and m", lambda: Izci() and Izci()),
                  ("n or m", lambda: Izci() or Izci()),
                  ("not n", lambda: not Izci()),
                  ("n & m", lambda: Izci() & Izci())):
    c = olc(islev)
    print(f"{ad:<12s} {len(c):5d}  {' '.join(c) if c else '(hiçbiri)'}")

print()
print("and/or ne döndürür:", repr("ol" and "cum"), repr("" or "cum"),
      repr(0 or []), "| not ne döndürür:", repr(not "ol"))
```

```
yazım                            çağrı  protokol
a < orta() < c  (ikisi de doğru)     3  orta() __lt__ __lt__
a < orta() and orta() < c            4  orta() __lt__ orta() __lt__
a < orta() < c  (ilki yanlış)        2  orta() __lt__

mantıksal yazım çağrı  protokol
n and m          0  (hiçbiri)
n or m           0  (hiçbiri)
not n            0  (hiçbiri)
n & m            1  __and__

and/or ne döndürür: 'cum' 'cum' [] | not ne döndürür: False
```

Zincirlenen karşılaştırma iki `__lt__` çağırıyor ama ortadaki işleneni **bir kez** üretiyor.
Elle açılmış yazım aynı iki karşılaştırmayı yapıyor ve ortayı **iki kez** üretiyor: dört
çağrıya karşı üç. Fark yalnız sayıda değil, doğrulukta da olabilir — ortadaki ifade bir yan
etki taşıyorsa iki yazım aynı programı vermez. Üçüncü satır zincirin ikinci yarısını da
gösteriyor: ilk karşılaştırma yanlış çıktığında ikincisi hiç yapılmıyor, çağrı **2**'de
kalıyor.

Alt tablo bir sınır çiziyor. `and`, `or` ve `not` hiçbir özel yöntem çağırmıyor; kayıtları
boş. Bunlar aşırı yüklenemez, çünkü yaptıkları iş bir işlem değil, bir **seçimdir**: işleneni
bir doğruluk sınamasından geçirip işlenenlerden **birini olduğu gibi** döndürürler. Son satır
bunu doğruluyor — `"ol" and "cum"` bir doğruluk değeri değil, `'cum'` dizgisini veriyor;
`0 or []` boş listeyi veriyor. Yalnız `not` bir doğruluk değeri üretir. Karıştırılmaya en
elverişli çift `and` ile `&`'dir: ilki bir seçim, ikincisi `__and__` çağıran bir işleçtir.

#### Özet

- Her işleç adı belli bir özel yöntemin kısaltmasıdır; on bir biçim **10 ayrı protokol**
  çağırır ve karşılığı olmayan biçim `TypeError` ile düşer. Öncelik, bu yöntemlerin hangi
  sırayla çağrılacağını belirler.
- `n + m` **`__add__`**, `n += m` **`__iadd__`** çağırır; ilki yeni nesne üretir, ikincisi
  var olanı değiştirir ve paylaşılan bir nesnede bu fark gözlenir.
- Soldaki işlenen işlemi karşılayamazsa sağdakinin ters yöndeki yöntemi (`__rmul__`)
  aranır; ikisi de yoksa işleç karşılıksız kalır.
- `x in n` önce `__contains__` arar (**1** çağrı); bulamazsa yineleme protokolüne düşer ve
  bulunmayan bir değer için **5** çağrı öder.
- Zincirlenen karşılaştırma ortadaki işleneni bir kez üretir ve ilk karşılaştırma yanlışsa
  ikincisini hiç yapmaz.
- `and`, `or` ve `not` özel yöntem çağırmaz; işlenenlerden birini olduğu gibi döndürürler ve
  bit düzeyi karşılıkları olan `&` ile `|`'dan ayrıdırlar.

#### Sonraki Adım

Bu derste `+` işlecinin `Izci` üzerinde yeni bir nesne ürettiği görüldü. Aynı işleç dizgiler
üzerinde de çalışır ve orada seçenek bile yoktur: dizgi değiştirilemez bir tiptir, dolayısıyla
`__iadd__` tanımlayamaz. Sonraki ders bunun bedelini sayar — bir dizginin küçük harfe
çevrilmesi, boşluklarının atılması ya da bir parçasının değiştirilmesi neden her seferinde
**yeni bir nesne** üretmek zorundadır, biçimlendirme yazımları bu üretimi nasıl tek adıma
indirir ve ard arda yapılan değişikliklerde kaç nesne ortaya çıkar?

### Dizgilerle Çalışma

Source: https://academia.sh/tr/kurslar/python-temelleri/dizgilerle-calisma

Dizgi değiştirilemez olduğu için her üretim yöntemi yeni bir nesne döndürür: yedi parçayı `+` ile birleştirmek 7 ara nesne kurar, `join` 1 kurar ve sekiz biçimlendirme yazımı 3 ayrı özel yöntem çağırır.

Önceki ders `+` işlecinin `__add__`, `+=` yazımının `__iadd__` çağırdığını ölçtü ve
değiştirilemez tiplerin `__iadd__` tanımlayamayacağını söyledi. Dizgi tam olarak böyle bir
tiptir; ondaki her işlem bu tek kısıttan türer.

Bu dersin sorusu dizgi yöntemlerinin listesi değildir. Soru şudur: bir tipin değiştirilemez
olması, üzerinde tanımlanabilecek yöntemleri nasıl **zorlar** ve bu zorlama sayı olarak ne
kadar tutar?

#### Yerinde Yazma Diye Bir Şey Yok

Bir dizginin bir karakterini değiştirmenin yolu yoktur. Sözdizim vardır — köşeli ayraçla
yazma — ama karşılığı olan yöntem tanımlı değildir.

- **DT31** — Ölçümde tek bir başlangıç dizgisi kullanılır ve üzerinde art arda dört işlem
  yapılır; her adımda sonucun bir önceki nesneyle **aynı nesne** olup olmadığı `is` ile
  sorulur. Hiçbir kimlik sayısı basılmaz.
- **DT32** — Seçilen dört işlem içeriği gerçekten değiştirir. Sonucu girdisiyle birebir aynı
  olan bir işlemin (örneğin boşluksuz bir dizgide `strip`) aynı nesneyi döndürüp
  döndürmemesi **gerçekleştirime bağlıdır** ve ölçüme sokulmamıştır.
- **DT33** — İkinci tablodaki yöntemler dizgiyi dönüştürmez, ona bir soru sorar; ölçülen
  şey dönen değerin **tipidir**.

```python
"""Dizgi degistirilemez: uretim yontemleri yeni nesne, sorgu yontemleri baska tip dondurur."""

HAM = "  12; kuzey; 18  "

try:
    HAM[0] = "x"
except TypeError as e:
    print("dizgiye yerinde yazma:", type(e).__name__)

ADIMLAR = (("başlangıç", lambda s: s),
           ("strip()", lambda s: s.strip()),
           ("upper()", lambda s: s.upper()),
           ("replace(';', ',')", lambda s: s.replace(";", ",")),
           ("split(',')", lambda s: s.split(",")))

print(f"\n{'adım':<20s} {'tip':<5s} {'önceki nesne mi':>15s}  değer")
onceki = HAM
for ad, islem in ADIMLAR:
    simdiki = islem(onceki)
    print(f"{ad:<20s} {type(simdiki).__name__:<5s} "
          f"{str(simdiki is onceki):>15s}  {simdiki!r}")
    onceki = simdiki
print("başlangıç dizgisi ölçümden sonra:", repr(HAM))

METIN = "12; kuzey; 18"
SORGULAR = (("startswith('12')", lambda s: s.startswith("12")),
            ("find('kuzey')", lambda s: s.find("kuzey")),
            ("count(';')", lambda s: s.count(";")),
            ("isdigit()", lambda s: s.isdigit()),
            ("len(s)", lambda s: len(s)))

print(f"\n{'sorgu yöntemi':<20s} {'dönen tip':<5s} değer")
for ad, islev in SORGULAR:
    deger = islev(METIN)
    print(f"{ad:<20s} {type(deger).__name__:<5s} {deger!r}")
print("sorgulardan sonra dizgi:", repr(METIN))
```

```
dizgiye yerinde yazma: TypeError

adım                 tip   önceki nesne mi  değer
başlangıç            str              True  '  12; kuzey; 18  '
strip()              str             False  '12; kuzey; 18'
upper()              str             False  '12; KUZEY; 18'
replace(';', ',')    str             False  '12, KUZEY, 18'
split(',')           list            False  ['12', ' KUZEY', ' 18']
başlangıç dizgisi ölçümden sonra: '  12; kuzey; 18  '

sorgu yöntemi        dönen tip değer
startswith('12')     bool  True
find('kuzey')        int   4
count(';')           int   2
isdigit()            bool  False
len(s)               int   13
sorgulardan sonra dizgi: '12; kuzey; 18'
```

İlk satır kısıtı adıyla koyuyor: köşeli ayraçla yazma `TypeError` veriyor, çünkü dizgi
tipinde bu işlemi karşılayan yöntem yok. Önceki derste bölme işleci için alınan sonucun
aynısıdır — kusur bir yasak değil, bir yokluktur.

Zincirdeki dört adımın dördünde de "önceki nesne mi" sütunu `False`. Her yöntem yeni bir
nesne kurdu ve girdisine dokunmadı; son satır bunu doğruluyor — dört işlemden sonra
başlangıç dizgisi olduğu gibi duruyor. Bunun bir seçim olmadığını görmek önemlidir. Bir
dizgi yöntemi girdisini değiştirmek **isteseydi bile** yapamazdı; tek yapabileceği şey yeni
bir nesne kurup döndürmektir. Değiştirilemezlik, yöntemin sözleşmesini kendisi yazar; bu
yüzden dizgi tipinde listedeki `append` ya da `sort` gibi yerinde çalışan bir yöntem
bulunmaz.

Buradan doğrudan bir kural çıkar: **dizgi yöntemlerinin dönüş değeri kullanılmazsa hiçbir şey
olmamıştır.** `metin.strip()` satırı tek başına yazıldığında yeni bir nesne kurar, ada
bağlamaz ve atar; `metin` eskisi gibi kalır. Bu, dile yeni gelen kodda en sık görülen sessiz
kusurdur.

Alt tablo yöntemlerin ikinci sınıfını ayırıyor. `startswith`, `find`, `count`, `isdigit` ve
`len` bir dizgi üretmez; sırasıyla bir doğruluk değeri, bir konum, bir sayım verir. Bunlar
dizgiyi dönüştürmez, ona bir soru sorar — dolayısıyla yeni nesne tartışmasının dışındadır.
`find` yöntemi aranan parçanın konumunu, bulamazsa `-1` döndürür; bir istisna fırlatmaz.
Aynı işi yapan `index` yöntemi ise bulamadığında `ValueError` fırlatır. İki yöntem arasındaki
seçim üslup değildir: bulunamamak beklenen bir durumsa dönüş değeriyle çalışan yazım, olmaması
gereken bir durumsa istisna fırlatan yazım seçilir. Aynı ayrımın dönüşüm tarafındaki
karşılığı sonraki dersin konusudur.

#### Dizgi Bir Dizidir

Üçüncü dersin yerleşik tip tablosunda `str` beş sütunun beşini de doldurmuştu: uzunluğu
sorulabiliyor, üzerinde döngü kurulabiliyor, üyelik sınanabiliyor, dilimlenebiliyor ve
toplanabiliyordu. Bu, dizginin yalnız bir metin değil, aynı zamanda bir **dizi** olduğu
anlamına gelir — ve dizi olmasının iki gözlenebilir sonucu vardır.

- **DT34** — Dilim ölçümünde her sonuç kaynak dizgiyle `is` ile karşılaştırılır; kaynağın
  tamamını isteyen bir dilim kullanılmaz, çünkü onun aynı nesneyi döndürüp döndürmemesi
  gerçekleştirime bağlıdır.
- **DT35** — Kaçış ölçümünde yazılan üç yazımın **karakter sayısı** okunur; sayı, yazımın
  kaç karakter ürettiğini gösterir, kaynakta kaç karakter yazıldığını değil.

```python
"""Dizgi bir dizidir: dilim, yineleme ve uyelik ayni protokollere dayanir."""

METIN = "12; kuzey; 18"

DILIMLER = (("[0]", lambda s: s[0]),
            ("[-1]", lambda s: s[-1]),
            ("[4:9]", lambda s: s[4:9]),
            ("[:2]", lambda s: s[:2]),
            ("[::-1]", lambda s: s[::-1]),
            ("[::4]", lambda s: s[::4]))

print(f"{'dilim':<8s} {'kaynakla aynı nesne mi':>22s}  değer")
for ad, islev in DILIMLER:
    parca = islev(METIN)
    print(f"{ad:<8s} {str(parca is METIN):>22s}  {parca!r}")

print(f"\nuzunluk {len(METIN)}, ilk dört öğe {list(METIN)[:4]}")
print("üyelik:", "'kuz' in metin ->", "kuz" in METIN,
      "| 'zey' in metin ->", "zey" in METIN,
      "| 'yez' in metin ->", "yez" in METIN)
print("listede aynı soru:", "[1, 2] in [1, 2, 3] ->", [1, 2] in [1, 2, 3],
      "| 2 in [1, 2, 3] ->", 2 in [1, 2, 3])

KACIS = (("'a\\tb'", "a\tb"), ("r'a\\tb'", r"a\tb"),
         ("'''iki\\nsatır'''", "iki\nsatır"))
print(f"\n{'yazım':<18s} {'karakter':>8s}  gerçek içerik")
for ad, deger in KACIS:
    print(f"{ad:<18s} {len(deger):8d}  {deger!r}")
```

```
dilim    kaynakla aynı nesne mi  değer
[0]                       False  '1'
[-1]                      False  '8'
[4:9]                     False  'kuzey'
[:2]                      False  '12'
[::-1]                    False  '81 ;yezuk ;21'
[::4]                     False  '1ky8'

uzunluk 13, ilk dört öğe ['1', '2', ';', ' ']
üyelik: 'kuz' in metin -> True | 'zey' in metin -> True | 'yez' in metin -> False
listede aynı soru: [1, 2] in [1, 2, 3] -> False | 2 in [1, 2, 3] -> True

yazım              karakter  gerçek içerik
'a\tb'                    3  'a\tb'
r'a\tb'                   4  'a\\tb'
'''iki\nsatır'''          9  'iki\nsatır'
```

Altı dilimin altısı da kaynaktan ayrı bir nesne veriyor. Dilimleme ilk derste ölçülen
`__getitem__` çağrısıdır ve dizgide de aynı kuralı taşır: yeni bir nesne kurulur, kaynak
olduğu gibi kalır. Eksi konum sondan sayar, üçüncü sayı adımı belirler; `[::-1]` yazımının
metni ters çevirmesi ayrı bir yöntemin işi değil, adımın eksi olmasının sonucudur.

Yineleme satırı dizginin öğelerinin **karakter** olduğunu gösteriyor: on üç uzunluğundaki
metin on üç öğe verir ve her öğe yine bir dizgidir. Python'da tek bir karakteri temsil eden
ayrı bir tip yoktur; bir karakter, uzunluğu bir olan dizgidir.

Üyelik satırı ise dizgiyi öteki dizilerden ayırıyor. `"kuz" in metin` **True** veriyor —
oysa aranan şey bir öğe değil, bir öğe **dizisi**. Aynı soru listede sorulduğunda
`[1, 2] in [1, 2, 3]` **False** verir, çünkü listede `in` yalnız öğe arar. Dizginin
`__contains__` yöntemi bilerek başka türlü yazılmıştır: alt dizgi arar. Sözdizim iki tipte
aynıdır, sözleşme değildir.

Son tablo yazım biçimlerini ayırıyor. Ters bölülü kaçış dizileri kaynakta iki karakterle
yazılır ama **tek** karakter üretir: `'a\tb'` yazımı üç karakterlik bir dizgidir. Başına
`r` konan ham dizgide kaçış çözülmez ve aynı kaynak dört karakter verir. Üç tırnaklı yazım
ise satır sonunu olduğu gibi taşır. Üçünde de kaynakta yazılan karakter sayısı ile üretilen
dizginin uzunluğu ayrıdır; dosya yolu ya da örüntü metni yazarken ham dizginin tercih edilme
nedeni budur.

#### Birleştirmenin Bedeli

Değiştirilemezliğin ölçülebilir bedeli, parça parça metin kurarken ortaya çıkar. Her `+`
işlemi yeni bir nesne kurduğu için, bir döngü içinde büyütülen dizgi yol boyunca kurulup
atılan ara nesneler bırakır.

- **DT36** — İki yöntem aynı yedi parçadan aynı metni kurar; ölçülen şey yol boyunca
  **kurulan ara nesne sayısıdır**, geçen süre değil. Artırmalı yöntemde her adımın sonucu
  `is` ile bir öncekiyle karşılaştırılarak sayılır; birleştirme tek nesne kurduğu için
  sayısı doğrudan yazılır.

```python
"""Birlestirmenin bedeli: kac ara nesne kuruluyor?"""

PARCALAR = ("kuzey", "; ", "12", "; ", "yamac", "; ", "18")


def artirmali(parcalar):
    metin = ""
    ara = 0
    for p in parcalar:
        onceki = metin
        metin = metin + p          # her adimda yeni bir dizgi
        ara += metin is not onceki
    return metin, ara


def birlestirerek(parcalar):
    metin = "".join(parcalar)      # tek adimda tek nesne
    return metin, 1


print(f"{'yöntem':<14s} {'parça':>5s} {'kurulan ara nesne':>18s}  sonuç")
for ad, islev in (("+ ile artırma", artirmali), ("join ile", birlestirerek)):
    sonuc, ara = islev(PARCALAR)
    print(f"{ad:<14s} {len(PARCALAR):5d} {ara:18d}  {sonuc!r}")

print("iki sonuç eşit mi:", artirmali(PARCALAR)[0] == birlestirerek(PARCALAR)[0])
```

```
yöntem         parça  kurulan ara nesne  sonuç
+ ile artırma      7                  7  'kuzey; 12; yamac; 18'
join ile           7                  1  'kuzey; 12; yamac; 18'
iki sonuç eşit mi: True
```

Yedi parça, artırmalı yöntemde **7** ara nesne kuruyor; `join` **1** kuruyor. Sonuçlar
birebir eşit. Örüntü parça sayısıyla doğrusal büyür: parça sayısı ikiye katlandığında
artırmalı yöntemin kurduğu nesne de ikiye katlanır, `join`'inki **1**'de kalır.

Farkın nedeni yöntemin daha iyi yazılmış olması değil, **bilgiye önceden sahip olmasıdır**.
`join` bütün parçaları başta görür, toplam uzunluğu hesaplayabilir ve sonucu tek seferde
kurar. `+` işleci ise her çağrıda yalnız iki parça görür; sonraki adımda ne geleceğini
bilemediği için o adımın sonucunu tam olarak kurmak zorundadır.

Aynı gerekçe, listeye toplayıp sonunda birleştirmenin neden yaygın bir kalıp olduğunu da
açıklar: liste **değiştirilebilir** bir tiptir, ona ekleme yapmak yeni nesne kurmaz. Metin
yalnız en sonda, bir kez üretilir.

`join` yönteminin bir koşulu vardır ve gözden kaçmaya elverişlidir: parçaların **hepsi
dizgi olmak zorundadır**. Aralarına bir sayı karışırsa yöntem onu kendiliğinden metne
çevirmez, `TypeError` fırlatır. Yöntemin çağrıldığı dizgi de parçalardan biri değil,
**ayraçtır**: `"; ".join(...)` yazımında araya giren metin, çağrının yapıldığı dizgidir.

#### Biçimlendirme Hangi Protokolü Çağırır

Metin üretmenin yazımları birden çoktur ve aynı görünürler. Kursun ölçüsü burada da işler:
her yazımın çağırdığı yöntem ayrıdır ve ürettikleri metin bu yüzden ayrışabilir.

- **DT37** — Ölçülen nesne üç metin protokolüne de katılır: `__str__`, `__repr__` ve
  `__format__`. Üçü ayrı metin üretir, böylece hangisinin çağrıldığı çıktıdan da okunabilir.
- **DT38** — `__format__` yönteminin kendisi hiçbir başka protokolü çağırmaz; kalıp
  boşsa kendi metnini kurar, doluysa kalıbı taşıdığı sayıya uygular. Böylece kayıt yalnız
  yazımın çağırdığını gösterir.

```python
"""Bicimlendirme yazimlari hangi ozel yontemi cagiriyor?"""

KAYIT = []


def kaydet(ad):
    KAYIT.append(ad)


class Olcum:
    """Uc metin protokolune de katilir ve her katilimini kaydeder."""

    def __init__(self, deger=12):
        self.deger = deger

    def __str__(self):
        kaydet("__str__")
        return f"olcum {self.deger}"

    def __repr__(self):
        kaydet("__repr__")
        return f"Olcum({self.deger})"

    def __format__(self, kalip):
        kaydet("__format__")
        return format(self.deger, kalip) if kalip else f"olcum {self.deger}"


BICIMLER = (
    ('f"{n}"', lambda n: f"{n}"),
    ('f"{n:>6}"', lambda n: f"{n:>6}"),
    ('f"{n!s}"', lambda n: f"{n!s}"),
    ('f"{n!r}"', lambda n: f"{n!r}"),
    ('"{}".format(n)', lambda n: "{}".format(n)),
    ('"%s" % n', lambda n: "%s" % n),
    ('str(n)', lambda n: str(n)),
    ('repr(n)', lambda n: repr(n)),
)

tekil = set()
print(f"{'yazım':<16s} {'protokol':<12s} ürettiği metin")
for ad, islev in BICIMLER:
    KAYIT.clear()
    metin = islev(Olcum())
    tekil.update(KAYIT)
    print(f"{ad:<16s} {' '.join(KAYIT):<12s} {metin!r}")

print(f"\n{len(BICIMLER)} yazım, ayrı protokol {len(tekil)}: "
      f"{', '.join(sorted(tekil))}")
```

```
yazım            protokol     ürettiği metin
f"{n}"           __format__   'olcum 12'
f"{n:>6}"        __format__   '    12'
f"{n!s}"         __str__      'olcum 12'
f"{n!r}"         __repr__     'Olcum(12)'
"{}".format(n)   __format__   'olcum 12'
"%s" % n         __str__      'olcum 12'
str(n)           __str__      'olcum 12'
repr(n)          __repr__     'Olcum(12)'

8 yazım, ayrı protokol 3: __format__, __repr__, __str__
```

Sekiz yazım **3 ayrı protokole** dağılıyor. Süslü ayraçlı yazım ile `format` yöntemi
`__format__` çağırıyor; yüzde işaretli yazım ile `str` yerleşiği `__str__` çağırıyor;
`repr` yerleşiği ile `!r` işaretçisi `__repr__` çağırıyor. Aynı nesne üç yazımda üç ayrı
metin veriyor ve hiçbiri ötekinin yerine geçmiyor.

İki nokta ayrıca okunmalı. Birincisi, `__format__` **kalıbı da alır**: ikinci satırda
`>6` kalıbı yönteme geçmiş ve yöntem onu kendi taşıdığı sayıya uygulamıştır. Hizalama,
basamak sayısı ve genişlik kuralları bu yolla nesnenin kendisine bırakılır. İkincisi,
işaretçiler bir kısayol değil, **protokol seçicidir**: `!r` yazımı çağrılacak yöntemi
`__format__`'tan `__repr__`'a çevirir.

Süslü ayraçlı yazımın ayrı bir yanı daha var: ayraçların içine bir değer değil, bir
**ifade** yazılabilir ve ifade orada değerlendirilir. İkinci dersin deyim–ifade ayrımı bu
yüzden burada da işler; ayraç içine bir atama konamaz, çünkü atama bir ifade değildir.
`format` yöntemi ise değerleri argüman olarak alır ve kalıbı ayrı tutar.

Üç yöntemin sözleşmeleri de ayrıdır. `__str__` okunabilir metin verir; `__repr__` nesneyi
tanıtan, çoğunlukla onu yeniden kuracak biçimde yazılmış metin verir — ölçümdeki
`'Olcum(12)'` bunun örneğidir. Bir nesne yalnız `__repr__` tanımlarsa `str` çağrısı da ona
düşer; ilk derste çıplak nesnede görülen davranışın kaynağı budur.

#### Özet

- Dizginin bir karakterine yazmak `TypeError` verir; karşılığı olan yöntem tanımlı değildir.
- Üretim yöntemlerinin dördünde de sonuç yeni bir nesnedir ve girdi olduğu gibi kalır;
  dönüş değeri kullanılmazsa hiçbir şey olmamıştır.
- Sorgu yöntemleri dizgi üretmez, başka tipte bir yanıt verir; `find` bulamadığında `-1`
  döndürür, istisna fırlatmaz.
- Dizgi bir dizidir: dilim `__getitem__` çağırır ve yeni nesne verir, öğeleri karakterdir,
  `in` sınaması ise listeden farklı olarak **alt dizgi** arar.
- Yedi parçayı artırmalı birleştirmek **7** ara nesne kurar, `join` **1** kurar; fark
  `join`'in bütün parçaları başta görmesinden gelir.
- Sekiz biçimlendirme yazımı **3 ayrı protokole** dağılır: `__format__`, `__str__` ve
  `__repr__`; `!r` gibi işaretçiler çağrılacak yöntemi değiştirir.

#### Sonraki Adım

Bu derste `str(n)` yazımının `__str__` çağırdığı ölçüldü — yani bir nesneyi metne çevirmek
de bir protokol çağrısıymış. Aynı soru ters yönde de sorulabilir: `int("12")` yazıldığında
ne çağrılıyor, `float(n)` bir nesneden neyi istiyor ve `int("kuzey")` gibi karşılığı olmayan
bir dönüşüm istendiğinde ne oluyor? Sonraki ders dönüşümün hangi özel yöntemlere dayandığını
sayar ve başarısız dönüşümün bir dönüş değeri değil, bir **istisna** ürettiğini gösterir.

### Tip Dönüşümü

Source: https://academia.sh/tr/kurslar/python-temelleri/tip-donusumu

Dönüşüm yerleşikleri sekiz yazımda ayrı özel yöntemler çağırır ve karşılığı olmayan iki yazım düşer; yirmi dizgi denemesinde 7 `ValueError` ile 4 `TypeError` ayrılır ve sayısal yükseltmenin sihir olmadığı `NotImplemented` ile gösterilir.

Önceki ders `str(n)` yazımının `__str__` çağırdığını ölçtü: bir nesneyi metne çevirmek de
bir protokol çağrısıymış. Aynı soru ters yönde de sorulabilir. `int(n)` yazıldığında ne
çağrılıyor, `float(n)` nesneden neyi istiyor ve karşılığı olmayan bir dönüşüm istendiğinde
ne oluyor?

Programlama Temelleri kursu açık ve örtük dönüşümü, kayıplı daraltmayı ve dış girdiyi
dönüştürmeden önce doğrulamayı zaten kurmuştu; gerçel sayıdan tam sayıya geçişin
yuvarlamayıp kestiği de orada gösterildi. **Bunlar tekrarlanmaz.** Burada ölçülen şey
dönüşümün **düzeneğidir**: hangi yerleşik hangi adı arıyor, aradığını bulamazsa hangi
istisnayı fırlatıyor ve iki ayrı sayısal tip toplandığında dilin yaptığı şey gerçekten bir
"dönüşüm" mü?

#### Dönüşüm de Bir Protokol Çağrısıdır

`int`, `float`, `str` ve `list` birer yerleşik işlevdir ve hepsi aynı biçimde çalışır:
verilen nesneden belli adı taşıyan bir yöntem isterler. Ad yoksa dönüşüm de yoktur.

- **DT39** — Ölçülen nesne dört dönüşüm protokolüne birden katılır ve her katılımını
  kaydeder; kayıttaki ünlem önekli öğe bir çağrıyı değil, bir istisnayı gösterir.
- **DT40** — İki karşı örnek kullanılır: hiçbir dönüşüm yöntemi tanımlamayan bir nesne ve
  yalnız `__int__` tanımlayıp dizin protokolünü taşımayan bir nesne.
- **DT41** — Dizinleme ölçümünde beş harfli sabit bir dizgi kullanılır; dönen harf,
  nesnenin dizin olarak verdiği sayıya bağlıdır.

```python
"""Donusum yerlesikleri hangi ozel yontemi cagiriyor?"""

KAYIT = []


def kaydet(ad):
    KAYIT.append(ad)


class Olcum:
    """Donusum protokollerine katilir ve her katilimini kaydeder."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __int__(self):
        kaydet("__int__")
        return sum(self.ogeler)

    def __float__(self):
        kaydet("__float__")
        return float(sum(self.ogeler))

    def __str__(self):
        kaydet("__str__")
        return f"olcum {self.ogeler}"

    def __index__(self):
        kaydet("__index__")
        return len(self.ogeler)

    def __iter__(self):
        kaydet("__iter__")
        self._i = 0
        return self

    def __next__(self):
        kaydet("__next__")
        if self._i >= len(self.ogeler):
            raise StopIteration
        deger = self.ogeler[self._i]
        self._i += 1
        return deger


class Sade:
    """Hicbir donusum yontemi tanimlamaz."""


class YalnizTamSayi:
    """Yalniz __int__ tanimlar; dizin protokolunu tasimaz."""

    def __int__(self):
        kaydet("__int__")
        return 2


def olc(islev):
    KAYIT.clear()
    try:
        sonuc = repr(islev())
    except Exception as e:
        KAYIT.append(f"!{type(e).__name__}")
        sonuc = "—"
    return list(KAYIT), sonuc


BICIMLER = (
    ("int(n)", lambda: int(Olcum())),
    ("float(n)", lambda: float(Olcum())),
    ("str(n)", lambda: str(Olcum())),
    ("list(n)", lambda: list(Olcum())),
    ("'kuzey'[n]", lambda: "kuzey"[Olcum((1, 2))]),
    ("int(Sade())", lambda: int(Sade())),
    ("'kuzey'[m]", lambda: "kuzey"[YalnizTamSayi()]),
    ("int(m)", lambda: int(YalnizTamSayi())),
)

print(f"{'yazım':<14s} {'çağrı':>5s} {'protokol':<32s} sonuç")
for ad, islev in BICIMLER:
    c, sonuc = olc(islev)
    print(f"{ad:<14s} {len(c):5d} {' '.join(c):<32s} {sonuc}")
```

```
yazım          çağrı protokol                         sonuç
int(n)             1 __int__                          6
float(n)           1 __float__                        6.0
str(n)             1 __str__                          'olcum [1, 2, 3]'
list(n)            5 __iter__ __next__ __next__ __next__ __next__ [1, 2, 3]
'kuzey'[n]         1 __index__                        'z'
int(Sade())        1 !TypeError                       —
'kuzey'[m]         1 !TypeError                       —
int(m)             1 __int__                          2
```

İlk üç satır kuralı kuruyor: `int` yerleşiği `__int__`, `float` yerleşiği `__float__`, `str`
yerleşiği `__str__` çağırıyor. Yerleşiğin adı ile yöntemin adı arasındaki karşılık tesadüf
değil, sözleşmedir. Dönüşümü yapan şey yerleşik değildir — yerleşik yalnız **kimin
yapacağını bilir**; işi nesnenin kendisi yapar ve sonucu döndürür.

Dördüncü satır bir dönüşümü daha ayırıyor. `list(n)` bir `__list__` yöntemi aramaz; **yineleme
protokolünü** kullanır ve öğeleri tek tek toplayarak yeni bir liste kurar. Üç öğe için
**5** çağrı ödeniyor: bir `__iter__` ve dört `__next__` — ilk dersteki "öğe sayısından bir
fazla" örüntüsünün aynısı. Buradan genel kural çıkar: bir koleksiyona dönüşüm ayrı bir
protokol istemez, var olanın üzerine kurulur. Aynı gerekçeyle `tuple(n)` ve `set(n)` de
yeni bir yöntem aramaz; üçü de aynı yineleme protokolünü tüketir ve yalnız kurdukları kabın
tipiyle ayrılırlar.

Beşinci satır ile yedinci satır birlikte okunmalı. Bir nesnenin dizin olarak kullanılması
`__int__` değil, **`__index__`** ister. İkisi ayrı sözleşmelerdir: `__int__` "bu nesne bir
tam sayıya **çevrilebilir**" demektir, `__index__` "bu nesne zaten bir tam sayı **konumdur**"
demektir. Ayrım keyfi değildir — bir gerçel sayı `__int__` taşır ama dizin olamaz, çünkü
dizinlemede kesme yapmak sessiz bir kusur olurdu. Yalnız `__int__` tanımlayan nesne son iki
satırda tam olarak bunu gösteriyor: `int(m)` çalışıyor, `'kuzey'[m]` `TypeError` ile düşüyor.

Altıncı satır ise ilk dersteki sonucun aynısı. Hiçbir dönüşüm yöntemi tanımlamayan nesnede
`int` çağrısı `TypeError` fırlatıyor. Sekiz yazımın **6**'sı bir yöntem buluyor, **2**'si
bulamıyor ve düşüyor. **Dönüşüm bir hak değil, bir yöntem meselesidir.**

#### Başarısız Dönüşüm Bir İstisnadır

Yukarıdaki tablo nesneden sayıya dönüşümü ölçtü. Dış dünyadan gelen veri ise dizgidir ve
orada durum ayrıdır: `str` tipi `__int__` tanımlamaz. `int("12")` yazıldığında yapılan şey
bir protokol çağrısı değil, metnin **çözümlenmesidir** — ve çözümleme başarısız olabilir.

- **DT42** — On girdi denenir: sekizi dizgi, ikisi başka tiptendir. Her girdi hem `int` hem
  `float` ile denenir; ölçülen şey dönen değer ya da istisnanın **türüdür**.
- **DT43** — Sayım yalnız istisnaları toplar; başarılı dönüşümler sayıma girmez.

```python
"""Basarisiz donusum: hangi girdi hangi istisnayi uretir?"""

GIRDILER = ("12", " 12 ", "+12", "1_2", "12.5", "", "kuzey", "0x1f", None, [1])


def dene(islev, girdi):
    try:
        return repr(islev(girdi)), "—"
    except Exception as e:
        return "—", type(e).__name__


print(f"{'girdi':<10s} {'int(...)':>10s} {'istisna':<12s}"
      f" {'float(...)':>12s} {'istisna':<12s}")
sayim = {}
for g in GIRDILER:
    i_deger, i_hata = dene(int, g)
    f_deger, f_hata = dene(float, g)
    for h in (i_hata, f_hata):
        if h != "—":
            sayim[h] = sayim.get(h, 0) + 1
    print(f"{repr(g):<10s} {i_deger:>10s} {i_hata:<12s}"
          f" {f_deger:>12s} {f_hata:<12s}")

print(f"\ntoplam deneme {2 * len(GIRDILER)}, "
      + ", ".join(f"{h} {n}" for h, n in sorted(sayim.items())))
print("taban verilirse:", "int('0x1f', 16) ->", int("0x1f", 16))
```

```
girdi        int(...) istisna        float(...) istisna     
'12'               12 —                    12.0 —           
' 12 '             12 —                    12.0 —           
'+12'              12 —                    12.0 —           
'1_2'              12 —                    12.0 —           
'12.5'              — ValueError           12.5 —           
''                  — ValueError              — ValueError  
'kuzey'             — ValueError              — ValueError  
'0x1f'              — ValueError              — ValueError  
None                — TypeError               — TypeError   
[1]                 — TypeError               — TypeError   

toplam deneme 20, TypeError 4, ValueError 7
taban verilirse: int('0x1f', 16) -> 31
```

Yirmi denemenin **11**'i istisnayla bitiyor ve istisnalar iki türe ayrılıyor: **7**
`ValueError`, **4** `TypeError`. Ayrım rastgele değildir ve öğrenilmesi gereken şey tam
olarak budur.

**`TypeError`** girdinin **tipi** dönüştürülemediğinde doğar. `None` ve liste satırlarında
sorun değerin ne olduğu değil, o tipin sayıya çevrilecek bir yöntem taşımamasıdır — birinci
tablonun `Sade` satırıyla aynı kusur sınıfıdır. **`ValueError`** ise tip doğru ama **değer**
uygun olmadığında doğar. `'kuzey'` bir dizgidir ve dizgi sayıya çevrilebilir; çevrilemeyen
şey o dizginin taşıdığı metindir.

Kabul edilen girdiler de öğreticidir. Baştaki ve sondaki boşluk atılır, artı işareti kabul
edilir, alt çizgi basamak ayracı olarak okunur. Ama `'12.5'` dizgisi `int` için geçersiz,
`float` için geçerlidir: `int` bir tam sayı gösterimi bekler ve kesirli metni sessizce
kesmez. Önceki kursta ölçülen kesme davranışı **gerçel sayı nesnesinden** tam sayıya
geçişte olur, dizgiden geçişte olmaz. `'0x1f'` satırı da aynı sınıftandır; on altılık
gösterim ancak taban açıkça verildiğinde okunur ve son satır bunu gösteriyor.

Buradan çıkan kural, önceki dersin `find` ile `index` ayrımının aynısıdır: **başarısız
dönüşüm bir dönüş değeri değil, bir istisna üretir.** Hiçbir yerleşik "çeviremedim" diyen
özel bir sayı döndürmez; dönmüş olsaydı o değer gerçek bir sonuçtan ayırt edilemezdi.
Sessiz bir kusur yerine gürültülü bir istisna seçilmiştir.

Tablonun kabul sütunu bir uyarı daha taşıyor. Programlama Temelleri kursu dönüşümden önce
biçimi sınamayı iki kalıptan biri olarak vermişti; ölçüm o kalıbın sınırını gösteriyor.
Yalnız basamaklardan oluşmayı sınayan bir ön denetim `' 12 '` ve `'+12'` girdilerini
reddeder — oysa ikisi de sorunsuz çevriliyor. Ön denetimin kabul ettiği küme ile dönüşümün
kabul ettiği küme aynı değildir ve iki kümeyi elde tutarlı kılmak dönüşüm kurallarını
ikinci kez yazmak demektir. Denemek ve düşen dönüşümü yakalamak bu yüzden daha dar bir
sözleşmedir: kabul kuralını tek bir yerde, dönüşümün kendisinde bırakır.

#### Dönüşüm Tersinir mi

Başarısız dönüşüm gürültü çıkarır ve bulunur. Asıl tehlike **başarılı görünen** dönüşümdedir:
bir değer çevrilir, hiçbir istisna doğmaz ve geri çevrildiğinde eskisi çıkmaz. Programlama
Temelleri kursu bu kaybı gösterdi; burada ölçülen şey kaybın **sınırıdır** — hangi yol
güvenceli, hangisi değil.

- **DT44** — Beş tam sayı iki ayrı yoldan gidip döner: dizgi üzerinden ve gerçel sayı
  üzerinden. Ölçülen şey dönen değerin başlangıçtakine **eşit** olup olmadığıdır.
- **DT45** — Gerçel sayı ölçümünde dönüş yolu nesneyi tanıtan metin üzerinden kurulur;
  seçilen dört değer büyüklük ve kesir bakımından ayrı uçlardandır.

```python
"""Donusum tersinir mi: gidip donunce ayni deger mi?"""

TAM = (12, -3, 2 ** 53, 2 ** 53 + 1, 10 ** 23)
GERCEL = (0.1, 3.5, 1 / 3, 1e300)

print(f"{'tam sayı':<26s} {'dizgi üzerinden':>16s} {'gerçel üzerinden':>17s}")
for n in TAM:
    print(f"{n!s:<26s} {str(int(str(n)) == n):>16s} "
          f"{str(int(float(n)) == n):>17s}")

print(f"\n{'gerçel sayı':<26s} {'dizgi üzerinden':>16s}")
for x in GERCEL:
    print(f"{x!r:<26s} {str(float(repr(x)) == x):>16s}")
```

```
tam sayı                    dizgi üzerinden  gerçel üzerinden
12                                     True              True
-3                                     True              True
9007199254740992                       True              True
9007199254740993                       True             False
100000000000000000000000               True             False

gerçel sayı                 dizgi üzerinden
0.1                                    True
3.5                                    True
0.3333333333333333                     True
1e+300                                 True
```

Dizgi sütunu beş satırda da `True`. Metin üzerinden gidip dönmek tam sayıda **her zaman**
tersinirdir, çünkü üçüncü derste ölçüldüğü gibi tam sayının sabit bir genişliği yoktur:
basamak sayısı ne olursa olsun metin de o kadar uzar. Aynı güvence gerçel sayı için de
geçerlidir — alt tablonun dördü de `True`. Bir gerçel sayıyı tanıtan metin, o sayıyı geri
verecek kadar basamak taşıyacak biçimde üretilir.

Üst tablonun **gerçel üzerinden** sütunu ise ikiye ayrılıyor: ilk üç değer sorunsuz dönüyor,
son ikisi dönmüyor.
Sınır tabloda görünüyor: iki üzeri elli üç sorunsuz, onun bir fazlası değil. Nedeni,
gerçel sayı gösteriminin taşıyabileceği anlamlı basamak sayısının sınırlı olmasıdır; o
sınırın üstünde bitişik iki tam sayı aynı gerçel sayıya düşer ve geri dönüşte ikisi
ayırt edilemez. Bu, Bilgisayarlar Nasıl Çalışır kursunda kurulan gösterim sınırının Python
tarafındaki karşılığıdır. Tabloda eşitlik sınamasının `False` çıkması, dönüşümün bir
istisna fırlatmadığını da gösteriyor: iki dönüşüm de başarıyla tamamlandı, yalnız sonuç
başlangıçtaki değer değildi.

Pratik sonuç tektir: **bir tam sayı değeri korunacaksa gerçel sayıya uğratılmaz.** Ne bir
istisna doğar ne bir uyarı çıkar; yalnız bir eşitlik sınaması yanlış çıkar. Bu ders boyunca
ölçülen istisnalar bu sınıfın tam tersidir — istisna, kusuru sesli hâle getirir.

#### Sayısal Yükseltme Sihir Değildir

Geriye örtük dönüşüm kalıyor. `1 + 2.5` yazıldığında tam sayı gerçel sayıya "yükseltilir" ve
sonuç gerçel çıkar. Bu, dilin sayılar için ayrı bir kural taşıdığı izlenimini verir. Ölçüm
bunun doğru olmadığını gösteriyor.

- **DT46** — İşlecin arkasındaki yöntemler doğrudan çağrılır; ölçülen şey dönen değerdir.
  Yöntem bir sonuç üretemediğinde döndürdüğü şey bir istisna değil, özel bir belirteçtir.
- **DT47** — Dört toplama denenir; ikisinde iki ayrı sayısal tip, ikisinde bir sayı ile bir
  dizgi karşılaşır.

```python
"""Sayisal yukseltme sihir degil, isleclerdeki dusustur."""

print("(1).__add__(2.5) ->", (1).__add__(2.5))
print("(2.5).__radd__(1) ->", (2.5).__radd__(1))
print("1 + 2.5 ->", 1 + 2.5, "| tipi", type(1 + 2.5).__name__)

print()
print(f"{'ifade':<14s} {'sonuç':>8s}  istisna")
for sol, sag in (("3", 4), (3, "4"), (3, 2.5), (True, 1)):
    try:
        sonuc, hata = repr(sol + sag), "—"
    except TypeError as e:
        sonuc, hata = "—", type(e).__name__
    print(f"{repr(sol) + ' + ' + repr(sag):<14s} {sonuc:>8s}  {hata}")
```

```
(1).__add__(2.5) -> NotImplemented
(2.5).__radd__(1) -> 3.5
1 + 2.5 -> 3.5 | tipi float

ifade             sonuç  istisna
'3' + 4               —  TypeError
3 + '4'               —  TypeError
3 + 2.5             5.5  —
True + 1              2  —
```

İlk satır belirleyici. Tam sayının `__add__` yöntemi bir gerçel sayı aldığında bir sonuç
değil, `NotImplemented` döndürüyor — "bu işi ben yapamam" anlamına gelen özel bir değer.
Bunun üzerine, dördüncü derste ölçülen düşüş işler: sağdaki işlenenin ters yöndeki yöntemi
denenir ve ikinci satırda görüldüğü gibi gerçel sayı işi üstlenir. Üçüncü satır sonucun
gerçel çıkmasının nedenini veriyor — hesabı yapan taraf gerçel sayı tipidir.

Yani "yükseltme" diye ayrı bir düzenek yoktur. Sayısal tipler birbirinin ters yöndeki
yöntemlerini karşıladıkları için birlikte çalışırlar; bu, dördüncü dersteki `__rmul__`
düşüşünün aynısıdır. Alt tablo sınırı da çiziyor: dizgi ile sayı hiçbir yönde birbirinin
yöntemini karşılamaz, dolayısıyla iki yazım da `TypeError` verir. Kimin solda kimin sağda
durduğu sonucu değiştirmiyor. Dizgi ile sayıyı toplamak isteyen kod, dönüşümü **kendisi**
yazmak zorundadır; dilin bu boşluğu kendiliğinden doldurmaması bir eksiklik değil, sessiz
sonuç üretmemek için verilmiş bir karardır.

Son satır bir Python ayrıntısını kapatıyor. `True + 1` ifadesi **2** veriyor, çünkü üçüncü
derste ölçüldüğü gibi doğruluk değerleri tam sayının bir alt tipidir. Burada bir dönüşüm
yapılmaz; nesne zaten bir tam sayıdır.

#### Özet

- Dönüşüm yerleşikleri adı belli özel yöntemler çağırır: `int` `__int__`, `float`
  `__float__`, `str` `__str__`; yöntem yoksa dönüşüm `TypeError` ile düşer.
- `list(n)` ayrı bir yöntem istemez, yineleme protokolünü kullanır ve üç öğe için **5**
  çağrı öder.
- Dizin olarak kullanılmak `__int__` değil `__index__` ister; ilki çevrilebilirliği, ikincisi
  nesnenin zaten bir konum olduğunu bildirir.
- Yirmi dizgi denemesinin **11**'i istisnayla biter: tip çevrilemiyorsa **4** `TypeError`,
  tip doğru ama değer uygunsuzsa **7** `ValueError`.
- Başarısız dönüşüm özel bir dönüş değeri değil, bir istisna üretir; aksi hâlde o değer
  gerçek bir sonuçtan ayırt edilemezdi. Sessiz olan kusur, gerçel sayıya uğrayıp geri dönen
  büyük tam sayıdadır.
- Sayısal yükseltme ayrı bir kural değildir: tam sayının `__add__` yöntemi `NotImplemented`
  döndürür ve işi gerçel sayının ters yöndeki yöntemi üstlenir.

#### Sonraki Adım

Bu ders bir konuyu kapatırken bir soruyu açık bıraktı. Dönüşüm başarısız olduğunda ortaya
bir **istisna** çıkıyor ve istisna, akışı bulunduğu yerden koparıp başka bir yere taşıyor —
yani bir kusur bildirimi değil, bir akış aracı. Sonraki konu akışın kendisini ele alacak ve
ilk dersi şu soruyla açacak: bir koşul yazıldığında sınanan şey nedir? `if` satırına bir
sayı, bir dizgi ya da boş bir liste konabilir; hepsi kabul edilir. Peki bir nesnenin **doğru
mu yanlış mı** sayıldığına kim karar veriyor — ve bu kararı veren yöntem yoksa ne oluyor?

## Denetim Akışı ve Fonksiyonlar

Karşılaştırma zincirleri ve doğruluk kuralları, yineleme protokolü üzerinden döngü, konumsal ile anahtarlı argümanlar, dört kapsam düzeyi ve yerleşiklerin sözleşmesi.

### Koşullar

Source: https://academia.sh/tr/kurslar/python-temelleri/kosullar

Koşul bir mantıksal değer beklemez, nesneyi doğruluk kuralına sokar: on iki sınamada sekiz protokol çağrısı ölçülür, dördü hiçbir yöntem çağırmadan doğru döner ve karşılaştırma zinciri orta terimi bir kez üretir.

Önceki ders tip dönüşümünü ölçtü ve dönüşümün başarısız olduğu yerde bir **istisna**
doğduğunu gösterdi: nesne kendisinden istenen tipe karşılık veremediğinde soru yanıtsız
kalmaz, akış kesilir. Dönüşüm nesneye "sen bir tam sayı olarak neye karşılık geliyorsun?"
diye sorar ve bu sorunun yanıtsız kalma hakkı vardır.

Koşul ifadesi de nesneye bir soru sorar, ama başka bir soru: **"sen doğru mu
sayılıyorsun?"** Bu sorunun yanıtı, dönüşümdekinin tersine, neredeyse hiçbir zaman
istisna olmaz — nesne yanıtı kendisi vermezse dil onun yerine bir yanıt üretir. Peki o
yanıtı kim veriyor? `if` deyimi nesnenin içine bakıp öğe mi sayıyor, tipine mi bakıyor,
yoksa nesneye mi soruyor? Bu dersin ölçtüğü şey budur.

#### Koşul Bir Mantıksal Değer Beklemez

Programlama Temelleri kursunun koşullu dallanma dersi koşulu **mantıksal değer üreten bir
ifade** olarak tanımlamıştı; o tanım dilden bağımsızdı ve dallanmayı bir **denetim yapısı**
olarak kurdu. Python'da tanım daha geniştir: `if` anahtar sözcüğünün arkasına **herhangi
bir nesne** yazılabilir ve dil onu bir doğruluk değerine kendisi indirger. Burada ölçülen
şey dallanmanın kendisi değil, o indirgemenin **hangi özel yöntemi çağırdığıdır**.

İndirgeme kuralına **doğruluk kuralı (truthiness)** denir ve üç basamaklıdır:

1. Nesnede `__bool__` tanımlıysa **o çağrılır**; döndürdüğü değer sonucun kendisidir.
2. Tanımlı değilse `__len__` **çağrılır**; sonuç sıfırdan büyükse nesne doğru sayılır.
3. İkisi de tanımlı değilse hiçbir yöntem çağrılmaz ve nesne **her zaman doğrudur**.

Üçüncü basamak kuralın en çok gözden kaçan yeridir. Boş bir nesnenin yanlış sayılması bir
dil kuralı değil, **nesnenin kendi bildirimidir**: bunu bildiren bir yöntem yoksa boşluk
diye bir kavram da yoktur. Sıradan bir nesne, içi ne olursa olsun, doğrudur.

#### Zincir Bir Kısaltmadır

Karşılaştırma işleçleri Python'da zincirlenebilir: `a < b < c` geçerli bir ifadedir. Bu
yazımın anlamı soldan sağa ikili karşılaştırma **değildir** — öyle olsaydı `a < b` bir
doğruluk değeri üretir, o değer `c` ile karşılaştırılırdı ve sonuç anlamsız çıkardı.

Zincirin anlamı `a < b and b < c` ifadesidir; iki farkla. Birincisi, orta terim `b`
**bir kez** değerlendirilir. Açık yazımda `b` iki kez yazılır ve bir işlev çağrısıysa iki
kez çalışır. İkincisi, `and` gibi zincir de **kısa devre** yapar: ilk karşılaştırma yanlış
çıkarsa ikincisi hiç kurulmaz, sağdaki terim üretilmez bile.

Zincirleme kuralı yalnız `<` ve `>` için değil, **bütün karşılaştırma işleçleri** için
geçerlidir; üyelik ve kimlik işleçleri de aynı sınıftadır. Bu yüzden `a < b <= c` kadar
`a is b is c` de geçerli bir zincirdir ve aynı iki güvenceyi taşır. Zincirin uzunluğunda
bir sınır yoktur: her ek terim, bir öncekiyle kendi karşılaştırmasını kurar ve
soldakilerin hepsi doğru çıkmadıkça sıraya hiç gelmez.

İkisi de gözle görülmez. Terimlerin kaç kez üretildiğini anlamanın tek yolu, üretimi
sayan bir kurgu kurmaktır.

#### Kısa Devrenin Döndürdüğü

`and` ve `or` işleçleri bir mantıksal değer döndürmez; **işlenenlerden birini** döndürür.
`x or y` ifadesi `x` doğruysa `x`'i, değilse `y`'yi verir. Döndürülen şey nesnenin
kendisidir, onun doğruluk değeri değil.

Bu ikisinin çağırdığı protokol de ayrıdır: `and` ve `or`, işlenenler üzerinde bir aritmetik
işleç gibi kendi özel yöntemini çağırmaz. Yaptıkları tek şey **sol işleneni doğruluk
kuralına sokmak** ve sonuca göre hangi işleneni döndüreceğine karar vermektir. Sağ işlenen,
gerekiyorsa döndürülür — sınanmaz.

Ölçümün varsayımları:

- **PA1** — Kâhin kurgunun kendisidir: hangi özel yöntemin çağrıldığını nesne kendi
  kaydettiği için bilinir; ölçüm dışarıdan bir gözlem değil, nesnenin kendi bildirimidir.
- **PA2** — Üç sınıf kullanılır. `Izci` yalnız `__len__` tanımlar, `Bosluksuz` ona
  `__bool__` ekler, `Sessiz` ikisini de tanımlamaz. Üç sınıf doğruluk kuralının üç
  basamağına birebir karşılık gelir.
- **PA3** — Her sınıf boş ve üç öğeli iki durumda sınanır; öğe sayısı sonucu değil,
  **sonucu kimin belirlediğini** ayırmak için değişir.
- **PA4** — Zincirin orta terimi bir işlev çağrısıyla üretilir ve her üretim kaydedilir;
  "kaç kez değerlendirildi" sorusunun ölçüsü bu kayıttır.
- **PA5** — `__lt__` uzunluğa göre karşılaştırır. Karşılaştırmanın **neye göre** yapıldığı
  ölçüme girmez; ölçülen şey **kaç kez** çağrıldığıdır.
- **PA6** — `Izci` yalnız `__lt__` tanımlar, karşıtı `__gt__` tanımsız bırakılır;
  yansımanın ölçülebilmesi için tanımsızlık kasıtlıdır.
- **PA7** — Sayılan çağrılar yalnız bu dersin kurduğu biçimlerden gelir; ortak tanımın
  diğer biçimleri bu ölçüme dahil değildir.

#### Ölçüm

```python
"""Dogruluk kurali: if n hangi ozel yontemi cagirir, hangi sirayla."""

KAYIT = []


def kaydet(ad):
    KAYIT.append(ad)


class Izci:
    """__len__ tanimli, __bool__ tanimsiz."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __len__(self):
        kaydet("__len__")
        return len(self.ogeler)

    def __lt__(self, o):
        kaydet("__lt__")
        return len(self.ogeler) < len(o.ogeler)


class Bosluksuz(Izci):
    """__bool__ tanimli: dogruluk sinamasi __len__'e dusmez."""

    def __bool__(self):
        kaydet("__bool__")
        return len(self.ogeler) > 0


class Sessiz:
    """Ne __bool__ ne __len__ tanimli."""

    def __init__(self, ogeler=()):
        self.ogeler = list(ogeler)


def olc(islev):
    KAYIT.clear()
    try:
        deger = islev()
    except Exception as e:
        KAYIT.append(f"!{type(e).__name__}")
        deger = None
    return list(KAYIT), deger


NESNELER = (
    ("Izci(3 oge)", lambda: Izci((1, 2, 3))),
    ("Izci(0 oge)", lambda: Izci(())),
    ("Bosluksuz(3 oge)", lambda: Bosluksuz((1, 2, 3))),
    ("Bosluksuz(0 oge)", lambda: Bosluksuz(())),
    ("Sessiz(3 oge)", lambda: Sessiz((1, 2, 3))),
    ("Sessiz(0 oge)", lambda: Sessiz(())),
)

print(f"{'nesne':<18s} {'if n':>6s} {'not n':>6s}  çağrılan protokol")
toplam = sinama = sessiz = 0
for ad, kur in NESNELER:
    n = kur()
    c1, d1 = olc(lambda: bool(n))
    c2, d2 = olc(lambda: not n)
    toplam += len(c1) + len(c2)
    sinama += 2
    sessiz += (len(c1) == 0) + (len(c2) == 0)
    print(f"  {ad:<16s} {str(d1):>6s} {str(d2):>6s}  {' '.join(c1) or '(yöntem yok)'}")
print(f"\nsınama {sinama}, çağrı {toplam}, hiçbir yöntemi çağırmayan {sessiz}")

URETIM = []


def uret(ad, n):
    URETIM.append(ad)
    return Izci(range(n))


def zincir():
    return uret("a", 1) < uret("b", 2) < uret("c", 3)


def acilim():
    return (uret("a", 1) < uret("b", 2)) and (uret("b", 2) < uret("c", 3))


def kisa_devre():
    return uret("a", 3) < uret("b", 2) < uret("c", 9)


print()
print(f"{'biçim':<16s} {'sonuç':>6s} {'__lt__':>7s} {'üretim':>7s}  sıra")
for ad, f in (("a<b<c", zincir), ("a<b and b<c", acilim),
              ("a<b<c  (yanlış)", kisa_devre)):
    URETIM.clear()
    c, d = olc(f)
    print(f"  {ad:<14s} {str(d):>6s} {c.count('__lt__'):7d}"
          f" {len(URETIM):7d}  {' '.join(URETIM)}")

print()
print(f"{'karşılaştırma':<24s} {'sonuç':>6s}  çağrılan protokol")
for ad, f in (("Izci(1) < Izci(2)", lambda: Izci((1,)) < Izci((1, 2))),
              ("Izci(2) > Izci(1)", lambda: Izci((1, 2)) > Izci((1,))),
              ("Sessiz(1) < Sessiz(2)", lambda: Sessiz((1,)) < Sessiz((1, 2)))):
    c, d = olc(f)
    print(f"  {ad:<22s} {str(d):>6s}  {' '.join(c)}")

print()
print(f"{'ifade':<26s} {'çağrı':>6s}  {'döndürülen tip':<16s} protokol")
BOS, DOLU = Izci(()), Bosluksuz((1, 2))
for ad, f in (("Izci(0) or Bosluksuz(2)", lambda: BOS or DOLU),
              ("Bosluksuz(2) or Izci(0)", lambda: DOLU or BOS),
              ("Izci(0) and Bosluksuz(2)", lambda: BOS and DOLU),
              ("bool(Izci(0) or Izci(0))", lambda: bool(Izci(()) or Izci(())))):
    c, d = olc(f)
    print(f"  {ad:<24s} {len(c):6d}  {type(d).__name__:<16s} {' '.join(c)}")
```

```
nesne                if n  not n  çağrılan protokol
  Izci(3 oge)        True  False  __len__
  Izci(0 oge)       False   True  __len__
  Bosluksuz(3 oge)   True  False  __bool__
  Bosluksuz(0 oge)  False   True  __bool__
  Sessiz(3 oge)      True  False  (yöntem yok)
  Sessiz(0 oge)      True  False  (yöntem yok)

sınama 12, çağrı 8, hiçbir yöntemi çağırmayan 4

biçim             sonuç  __lt__  üretim  sıra
  a<b<c            True       2       3  a b c
  a<b and b<c      True       2       4  a b b c
  a<b<c  (yanlış)  False       1       2  a b

karşılaştırma             sonuç  çağrılan protokol
  Izci(1) < Izci(2)        True  __lt__
  Izci(2) > Izci(1)        True  __lt__
  Sessiz(1) < Sessiz(2)    None  !TypeError

ifade                       çağrı  döndürülen tip   protokol
  Izci(0) or Bosluksuz(2)       1  Bosluksuz        __len__
  Bosluksuz(2) or Izci(0)       1  Bosluksuz        __bool__
  Izci(0) and Bosluksuz(2)      1  Izci             __len__
  bool(Izci(0) or Izci(0))      2  bool             __len__ __len__
```

#### Üç Basamağın Bedeli

Üst tablo doğruluk kuralının üç basamağını yan yana koyuyor. **On iki sınamada sekiz
çağrı** var; aradaki dört sınama **hiçbir yöntemi çağırmadan** sonuç üretiyor.

İlk iki satır ikinci basamak: `Izci` `__bool__` tanımlamadığı için sınama `__len__`'e
düşüyor ve sonucu öğe sayısı belirliyor. Üçüncü ve dördüncü satır birinci basamak: aynı
sınıfa `__bool__` eklendiği anda `__len__` **hiç çağrılmıyor**. İkisi de tanımlıyken hangi
yöntemin çağrılacağı sorusu yok — `__bool__` varsa `__len__` sınamaya katılmaz.

Son iki satır üçüncü basamak ve tablonun asıl sonucu burada. `Sessiz(0 oge)` **boş**, ama
sınama **doğru** veriyor. Üstelik bunu bir yöntem çağırarak değil, **hiç çağırmayarak**
yapıyor. Nesnenin üç öğesi mi var, sıfır mı — sonucu değiştirmiyor, çünkü sonucu üretecek
bir yöntem yok. Bu satırın pratik karşılığı şudur: kendi yazdığı bir sınıfın boş örneğini
`if` ile sınayan bir program, `__len__` ya da `__bool__` yazmayı unutmuşsa **her zaman
doğru** dalını alır ve bunu bildiren hiçbir belirti çıkmaz.

`not n` sütunu aynı zincirin bir kez daha yürüdüğünü gösteriyor: olumsuzlama ayrı bir
protokol değil, aynı sınamanın sonucunun tersidir. Sütunlardaki tek fark `Sessiz`
satırlarında görülüyor — `not` bir nesne değil, gerçek bir mantıksal değer döndürüyor.

#### Zincir Kaç Terim Üretiyor

Orta tablo zincirin kısaltma olduğunu sayıyla gösteriyor. `a<b<c` ile açık yazımın
`__lt__` sayısı aynı: **ikisi de 2**. Karşılaştırma sayısında bir kazanç yok.

Fark üretim sütununda. Zincir üç terim üretiyor, açık yazım **dört**: `b` iki kez
üretiliyor. Bu, okunabilirlik değil **anlam** farkıdır. Orta terim bir işlev çağrısıysa
o işlev iki kez çalışır; yan etkisi varsa yan etki iki kez oluşur; her çağrıda başka bir
değer döndürüyorsa iki karşılaştırma **birbirinden habersiz iki ayrı değeri** sınar. Açık
yazım, zincirin verdiği güvenceyi vermez.

Üçüncü satır kısa devreyi ölçüyor. İlk karşılaştırma yanlış çıktığı için `__lt__`
**1** kez çağrılıyor ve üretim **2**'de duruyor: `c` hiç kurulmuyor. Zincirin sağ ucundaki
terim, sola bakılmadan üretilmeyecek bir şeydir.

#### Ters Yönde Bulunan Protokol

Üçüncü tablo karşılaştırmanın kendi düşme kuralını gösteriyor ve ortadaki satır dersin en
sert sonucudur. `Izci` sınıfı `__lt__` tanımlar, `__gt__` tanımlamaz. Buna rağmen
`Izci(2) > Izci(1)` çalışıyor — ve çağrılan yöntem **`__lt__`**.

Kural şudur: bir karşılaştırmada sol işlenenin yöntemi bulunamazsa dil **sağ işlenenin
karşıt yöntemini** dener. `a > b` için önce `a` üzerinde `__gt__` aranır; yoksa `b`
üzerinde `__lt__` çağrılır ve işlenenler yer değiştirir. Sonuç doğru çıkar, çünkü
"`b`, `a`'dan küçüktür" ile "`a`, `b`'den büyüktür" aynı iddiadır.

Bunun sonucu doğrudan ikinci iddiadır: **yazılan işleç, çağrılan yöntemin adını
söylemez.** İki satır sonucu `True` veriyor, ikisi de aynı yöntemi çağırıyor, ama biri
`<` biri `>` yazıyor. Sözdizime bakarak hangi yöntemin çalıştığını kestirmek olanaksızdır;
bunu yalnız ölçüm söyler.

Üçüncü satır kuralın sonunu gösteriyor. `Sessiz` ne `__lt__` ne `__gt__` tanımlar; ne sol
işlenende ne sağ işlenende bir yöntem bulunuyor ve karşılaştırma bir sonuç değil,
`TypeError` üretiyor. Doğruluk sınamasının üçüncü basamağı sessizce doğru dönerken,
karşılaştırmanın son basamağı **istisnayla** biter: bir sözdizim için varsayılan davranış
var, öbürü için yok. Protokolü olmayan nesne, o sözdizimi hiç taşımaz.

#### Koşul Deyimi ile Koşul İfadesi

Doğruluk kuralı yalnız `if` deyiminde çalışmaz. Aynı indirgeme `while` koşulunda, `and` ve
`or` işleçlerinde, `not` işlecinde ve **koşul ifadesinde** de yürür.

Koşul ifadesi, önceki konunun deyim ile ifade ayrımını kullanır: `if` bir **deyimdir**,
değer üretmez ve bir atamanın sağına yazılamaz. Bunun ifade karşılığı `a if k else b`
yazımıdır ve bir **değer üretir** — dolayısıyla bir atamanın sağına, bir argümanın yerine
ya da bir listenin içine konabilir.

İkisi arasındaki fark yalnız yazımdadır; doğruluk kuralı ikisinde de aynı üç basamağı
yürütür ve `k` yerine yazılan nesne aynı ölçümden geçer. Kısa devre de korunur: koşul
ifadesinde `a` ile `b` dallarından **yalnız biri** değerlendirilir. Bu, koşul ifadesini
`and` ve `or` zincirlerinden ayıran şeydir — orada döndürülen değer işlenenlerden biriydi,
burada seçilen dalın kendisidir ve seçilmeyen dal hiç kurulmaz.

#### Döndürülen Nesnenin Tipi

Alt tablo `and` ile `or` işleçlerinin ne döndürdüğünü gösteriyor ve üç satırının üçünde de
dönen şey **bir mantıksal değer değil**.

İlk satır: sol işlenen `Izci(0)` boş, sınama `__len__` ile yanlış çıkıyor, `or` sağ
işleneni döndürüyor — dönen tip `Bosluksuz`. İkinci satır: sol işlenen bu kez doğru,
`__bool__` ile sınanıyor ve `or` **sol işleneni** döndürüyor. Üçüncü satırda `and` tersini
yapıyor: sol işlenen yanlış olduğu için sonuç doğrudan **o** oluyor, tip `Izci`.

Üç satırın da çağrı sayısı **1**. Sağ işlenen hiçbir zaman sınanmıyor; yalnız döndürülüyor.
Bunun sonucu, `or` ile yazılan varsayılan değer kalıbının Python'da tam olarak ne yaptığıdır:
sol işlenen doğruluk kuralından yanlış çıkarsa sağdaki verilir — **boş** olduğu için değil,
**yanlış sayıldığı** için.

Son satır ikisini ayırıyor. `bool()` yerleşiği araya girdiğinde dönen tip gerçekten `bool`
oluyor ve çağrı sayısı **2**'ye çıkıyor: bir kez `or` sol işleneni sınıyor, bir kez de
`bool()` sonucu sınıyor. Aynı ifadeyi mantıksal değere çevirmenin bedeli fazladan bir
protokol çağrısıdır.

#### Özet

- Python'da koşul mantıksal değer beklemez; `if` arkasındaki her nesne **doğruluk
  kuralından** geçirilir ve ölçülen şey bu indirgemenin çağırdığı özel yöntemdir.
- Kural üç basamaklıdır: `__bool__` varsa o çağrılır, yoksa `__len__`'e düşülür, o da
  yoksa **hiçbir yöntem çağrılmadan** nesne doğru sayılır — on iki sınamanın dördü bu
  basamakta ve boş nesne bile doğru döner.
- `__bool__` tanımlıyken `__len__` sınamaya hiç katılmaz; iki yöntem yarışmaz, ilki
  ikincisini devre dışı bırakır.
- Karşılaştırma zinciri `a<b<c` ile açık yazım aynı sayıda `__lt__` çağırır (**2**), ama
  zincir orta terimi **bir kez** üretir, açık yazım **iki kez**; zincir yanlış çıktığında
  sağ terim hiç üretilmez.
- Karşılaştırmanın kendi düşme kuralı vardır: sol işlenende yöntem yoksa sağ işlenenin
  karşıtı çağrılır — `>` yazılan bir ifade `__lt__` çalıştırır; ikisi de yoksa sonuç
  bir değer değil, `TypeError` olur.
- `and` ile `or` bir mantıksal değer değil, **işlenenlerden birini** döndürür; yalnız sol
  işleneni doğruluk kuralına sokarlar, sağ işleneni sınamazlar.

#### Sonraki Adım

Doğruluk kuralı tek bir soruyu bir kez sorar ve yanıtı bir dalla biter. Döngü ise aynı
soruyu değil, **başka bir soruyu tekrar tekrar** sorar: sırada bir öğe var mı? Programlama
Temelleri kursunda döngü bir denetim yapısıydı; kaç tur döneceği koşula ya da koleksiyonun
uzunluğuna bağlıydı. Python'da döngü bir **protokoldür** ve turların bitmesine karar veren
şey bir koşul değildir. Sonraki ders üç öğelik bir nesnede döngünün kaç kez `__next__`
çağırdığını sayar — ve o sayının neden öğe sayısından bir fazla olduğunu.

### Döngüler

Source: https://academia.sh/tr/kurslar/python-temelleri/donguler

Döngü bir denetim yapısı değil, bir protokoldür: üç öğelik nesnede dört kez __next__ çağrılır, sonuncusu StopIteration fırlatır ve döngüyü bitiren şey koşul değil, o istisnadır.

Önceki ders doğruluk kuralını ölçtü: `if` arkasındaki nesne bir soruya sokuluyor ve o
sorunun yanıtı bir dalla bitiyordu. Sınama bir kez yapılıyor, sonuç bir kez okunuyordu.

Döngü aynı soruyu tekrarlamaz, **başka bir soruyu** tekrarlar: sırada bir öğe var mı?
Programlama Temelleri kursunun döngüler dersi bu yapıyı bir **denetim yapısı** olarak
kurdu — turların sayısı ya koleksiyonun uzunluğuna ya bir koşula bağlıydı ve döngünün
sonlanması, her turda koşulu yanlışlamaya yaklaşan bir değişiklikle güvenceye alınıyordu.
Python'da döngü bir **protokoldür** ve o dersin sonlanma gerekçesi burada geçerli değildir:
`for` döngüsünün sınadığı bir koşul yoktur. Peki turların bittiğine ne karar veriyor? Bu
dersin ölçtüğü şey budur — ve yanıtın bir hata sınıfı olduğu ortaya çıkacaktır.

#### Döngü Neyi Çağırır

`for x in n` yazımı üç adımlıdır ve üçü de gizlidir.
Önce nesneden bir **yineleyici (iterator)** istenir: `__iter__` çağrılır.
Sonra her turda o yineleyiciden bir sonraki öğe
istenir: `__next__` çağrılır. Üçüncü adım bitiştir — ve bitişi bildiren şey bir dönüş
değeri değildir.

Bir dönüş değeri olamazdı. `__next__` herhangi bir nesne döndürebilir; hangi değeri
seçersek seçelim, o değer koleksiyonun gerçek bir öğesi olabilir ve "bitti" ile "işte
öğen" birbirine karışırdı. Bu yüzden bitiş kanaldan çıkarılmıştır: `__next__`, verecek
öğesi kalmadığında bir **istisna** fırlatır — `StopIteration`. Döngüyü bitiren şey budur.

Bunun doğrudan bir sonucu var: son bir çağrı, hiçbir öğe üretmeyen bir çağrı, her zaman
yapılır. Öğe sayısı ne olursa olsun `__next__` **bir fazla** çağrılır ve o fazladan çağrı
döngünün bitiş bildirimidir.

#### Aynı Döngünün Elle Yazımı

Protokolün gerçekten bir kısaltma olduğunu göstermenin yolu, `for` kullanmadan aynı işi
yapmaktır: yineleyiciyi `iter()` ile al, bir sonsuz döngü kur, her turda `next()` çağır ve
`StopIteration` yakalandığında çık. İki yazım aynı protokol dizisini üretiyorsa `for`
gerçekten bu üç adımın kısaltmasıdır.

Ölçüme iki nesne daha girer. `Izci` hem `__iter__` hem `__getitem__` tanımlar; ikisi de
yinelemeye yeter, ama hangisinin kullanıldığını ancak sayım söyler. `Dizinli` yalnız
`__getitem__` tanımlar ve `__iter__` yoktur — böyle bir nesnenin döngüye girip
giremeyeceği, girerse hangi protokolü izleyeceği ikinci sorudur.

Ölçümün varsayımları:

- **PA8** — Kâhin kurgunun kendisidir: her özel yöntem çağrıldığında adını kaydeder;
  "kaç kez çağrıldı" sorusunun ölçüsü bu kayıttır.
- **PA9** — `Izci` ortak tanımdaki davranışını korur: `__iter__` sayacı sıfırlayıp
  kendisini döndürür, `__next__` öğeler bitince `StopIteration` fırlatır.
- **PA10** — Öğe sayısı sıfırdan dörde kadar değiştirilir; sayının kendisi değil, **çağrı
  sayısıyla arasındaki fark** ölçülür.
- **PA11** — `break` ikinci öğede çalıştırılır; kesme noktasının yeri sabittir ve turların
  erken bitmesinin çağrı sayısına etkisini ayırmak için seçilmiştir.
- **PA12** — Ölçüm listeye alma yazımı kullanmaz, yalnız açık `for` gövdesi kullanır;
  amacı, sayılan çağrıların yalnız yineleme protokolünden gelmesini güvenceye almaktır.
- **PA13** — `Dizinli` yalnız `__getitem__` tanımlar; `__iter__` tanımsızlığı kasıtlıdır
  ve ikinci protokolün ölçülebilmesi için gereklidir.
- **PA14** — `while` ölçümünde gövde her turda bir öğe eksiltir; sonlanma güvencesi
  buradan gelir ve sınama sayısıyla tur sayısı ayrı ayrı okunur.
- **PA15** — İç içe döngü ölçümü iki durumda yapılır: iki döngü **aynı** nesne üzerinde
  ve **ayrı** nesneler üzerinde. `Izci.__iter__` ortak tanımdaki gibi kendisini döndürür
  ve sayacı sıfırlar; ölçülen şey bu iki kararın tur sayısına etkisidir.

#### Ölçüm

```python
"""Yineleme protokolu: dongu kac kez __next__ cagirir ve dongüyü ne bitirir."""

KAYIT = []


def kaydet(ad):
    KAYIT.append(ad)


class Izci:
    """Hem __iter__ hem __getitem__ tanimli."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __iter__(self):
        kaydet("__iter__")
        self._i = 0
        return self

    def __next__(self):
        kaydet("__next__")
        if self._i >= len(self.ogeler):
            raise StopIteration
        deger = self.ogeler[self._i]
        self._i += 1
        return deger

    def __getitem__(self, k):
        kaydet("__getitem__")
        return self.ogeler[k]

    def __len__(self):
        kaydet("__len__")
        return len(self.ogeler)


class Dizinli:
    """Yalniz __getitem__ tanimli: __iter__ yok."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __getitem__(self, k):
        kaydet("__getitem__")
        return self.ogeler[k]


def olc(islev):
    KAYIT.clear()
    try:
        islev()
    except Exception as e:
        KAYIT.append(f"!{type(e).__name__}")
    return list(KAYIT)


TUR = []


def tam():
    for x in Izci():
        TUR.append(x)


def kesilen():
    for x in Izci():
        TUR.append(x)
        if x == 2:
            break


def sonlu_else():
    for x in Izci():
        TUR.append(x)
    else:
        kaydet("else")


def kesilen_else():
    for x in Izci():
        TUR.append(x)
        if x == 2:
            break
    else:
        kaydet("else")


def elle():
    y = iter(Izci())
    while True:
        try:
            TUR.append(next(y))
        except StopIteration:
            break


def dorduncu():
    y = iter(Izci())
    for _ in range(4):
        next(y)


BICIMLER = (("for x in n", tam), ("for + break", kesilen),
            ("for + else", sonlu_else), ("for + break + else", kesilen_else),
            ("iter/next elle", elle), ("dördüncü next", dorduncu))

print(f"{'biçim':<20s} {'tur':>4s} {'__next__':>9s} {'else':>5s}  protokol dizisi")
for ad, f in BICIMLER:
    TUR.clear()
    c = olc(f)
    p = [a for a in c if a != "else"]
    print(f"  {ad:<18s} {len(TUR):4d} {c.count('__next__'):9d}"
          f" {('evet' if 'else' in c else '-'):>5s}  {' '.join(p)}")

print()
print(f"{'öğe':>4s} {'tur':>4s} {'__next__':>9s}  fark")
for n in range(5):
    TUR.clear()

    def say(n=n):
        for x in Izci(range(n)):
            TUR.append(x)
    c = olc(say)
    print(f"  {n:2d} {len(TUR):4d} {c.count('__next__'):9d}"
          f"  {c.count('__next__') - n:+d}")

print()
print(f"{'nesne':<10s} {'tanımlı':<24s} {'çağrı':>6s}  protokol dizisi")
for ad, kur, tanimli in (
        ("Izci", Izci, "__iter__ ve __getitem__"),
        ("Dizinli", Dizinli, "yalnız __getitem__")):
    def gez(k=kur):
        for _ in k():
            pass
    c = olc(gez)
    print(f"  {ad:<8s} {tanimli:<24s} {len(c):6d}  {' '.join(c)}")
print("  dördüncü dizin doğrudan:", " ".join(olc(lambda: Dizinli()[3])))


DIS = []


def ic_ice_ayni():
    n = Izci()
    for x in n:
        DIS.append(x)
        for y in n:
            TUR.append(y)


def ic_ice_ayri():
    for x in Izci():
        DIS.append(x)
        for y in Izci():
            TUR.append(y)


print()
print(f"{'iç içe döngü':<16s} {'dış tur':>8s} {'iç tur':>7s}"
      f" {'__iter__':>9s} {'__next__':>9s}")
for ad, f in (("aynı nesne", ic_ice_ayni), ("ayrı nesneler", ic_ice_ayri)):
    TUR.clear()
    DIS.clear()
    c = olc(f)
    print(f"  {ad:<14s} {len(DIS):8d} {len(TUR):7d}"
          f" {c.count('__iter__'):9d} {c.count('__next__'):9d}")


def dongu_while():
    n = Izci()
    while n:
        n.ogeler.pop()


print()
c = olc(dongu_while)
print(f"while n  (3 öğe boşalana dek): çağrı {len(c)}  {' '.join(c)}")
```

```
biçim                 tur  __next__  else  protokol dizisi
  for x in n            3         4     -  __iter__ __next__ __next__ __next__ __next__
  for + break           2         2     -  __iter__ __next__ __next__
  for + else            3         4  evet  __iter__ __next__ __next__ __next__ __next__
  for + break + else    2         2     -  __iter__ __next__ __next__
  iter/next elle        3         4     -  __iter__ __next__ __next__ __next__ __next__
  dördüncü next         0         4     -  __iter__ __next__ __next__ __next__ __next__ !StopIteration

 öğe  tur  __next__  fark
   0    0         1  +1
   1    1         2  +1
   2    2         3  +1
   3    3         4  +1
   4    4         5  +1

nesne      tanımlı                   çağrı  protokol dizisi
  Izci     __iter__ ve __getitem__       5  __iter__ __next__ __next__ __next__ __next__
  Dizinli  yalnız __getitem__            4  __getitem__ __getitem__ __getitem__ __getitem__
  dördüncü dizin doğrudan: __getitem__ !IndexError

iç içe döngü      dış tur  iç tur  __iter__  __next__
  aynı nesne            1       3         2         6
  ayrı nesneler         3       9         4        16

while n  (3 öğe boşalana dek): çağrı 4  __len__ __len__ __len__ __len__
```

#### Bir Fazla Çağrı

Üst tablonun ilk satırı ortak tanımın üçüncü iddiasıdır. Üç öğelik bir nesnede döngü
**üç tur** dönüyor ama `__next__` **dört kez** çağrılıyor. Fazladan olan dördüncü çağrı
hiçbir öğe üretmiyor; üretmediğini bildirmek için var.

İkinci tablo bunun tek bir örneğe bağlı olmadığını gösteriyor. Öğe sayısı sıfırdan dörde
çıkarken fark sütunu değişmiyor: **her satırda +1**. Sıfır öğeli nesnede bile bir çağrı
var — hiç tur dönmeyen döngü bile bir kez sormak zorunda, çünkü sormadan boş olduğunu
bilemez. Çağrı sayısı öğe sayısının bir fonksiyonu değil, **öğe sayısı artı bitiş
bildirimidir**.

Son satır o bildirimi doğrudan gösteriyor. Yineleyici elle alınıp `next()` dört kez
çağrıldığında dördüncüsü bir değer döndürmüyor, **`StopIteration` fırlatıyor**. `for`
döngüsünde bu istisnayı görmeyiz çünkü döngü onu kendisi yakalar ve akışı sessizce
gövdenin dışına taşır. Görünmemesi olmadığı anlamına gelmez: **döngünün her normal
bitişinde bir istisna doğar ve yakalanır.**

Buradan çıkan okuma ortak tanımın üçüncü iddiasıdır: istisna bir hata değil, **protokolün
parçasıdır**. Bir hata olsaydı sıra dışı bir durumu bildirirdi; oysa bu istisna en
sıradan durumu bildiriyor — koleksiyon bitti.

#### Kısaltmanın Açılımı

Beşinci satır `for` yazımının gerçekten bir kısaltma olduğunu kanıtlıyor. Elle yazılan
biçim — `iter()`, sonsuz döngü, `next()`, `StopIteration` yakalama — **birebir aynı
protokol dizisini** üretiyor: bir `__iter__`, dört `__next__`. İki yazım arasında
yürütme farkı yok; fark yalnız `try` bloğunun görünür olmasında.

İkinci ve dördüncü satırlar `break` ile kesilen döngüyü ölçüyor ve buradaki sayı diğer
hepsinden ayrılıyor: **iki tur, iki çağrı**. Fazladan çağrı **yok**. `break` döngüyü
protokolün dışından bitiriyor; üçüncü öğe hiç istenmiyor, `StopIteration` hiç doğmuyor.
Kesilen döngü, tek fazladan çağrının ortadan kalktığı tek biçimdir.

Aynı konunun öbür denetim deyimi `continue` bu tabloda ayrı bir satır gerektirmez, çünkü
protokole hiç dokunmaz: gövdenin kalanını atlar ve döngüyü **bir sonraki `__next__`
çağrısına** taşır. Turların sayısı değişmez, fazladan çağrı yerinde durur. `break` ile
arasındaki fark tam da budur — biri protokolün içinde bir adım atlar, öbürü protokolü
terk eder.

Bu, `else` sütununu da açıklıyor. Döngüye bağlı `else` bloğu, döngü **kendi protokolüyle**
bittiğinde çalışır; `break` ile kesildiğinde çalışmaz. Üçüncü satırda `else` çalışıyor,
dördüncüde çalışmıyor. Yani `else`, "hiç `break` olmadan bitti mi?" sorusunun yanıtıdır —
bir başka deyişle, **`StopIteration` gerçekten fırlatıldı mı?** Aranan bir öğeyi bulunca
`break` eden ve bulunamadığında `else` bloğunda davranan arama kalıbı doğrudan bu ayrımın
üzerine kuruludur.

#### İkinci Protokol

Üçüncü tablo hangi protokolün seçildiğini ölçüyor ve önceki dersin örüntüsünü tekrarlıyor.

`Izci` hem `__iter__` hem `__getitem__` tanımlar. Döngü **`__iter__`'i seçiyor** ve
`__getitem__` hiç çağrılmıyor. Doğruluk kuralında `__bool__` `__len__`'i nasıl devre dışı
bıraktıysa burada da `__iter__` `__getitem__`'i devre dışı bırakıyor; iki yöntem
yarışmıyor, ilki ikincisini kapatıyor.

`Dizinli` satırı ikinci basamağı gösteriyor. Bu nesnede `__iter__` yok, ama döngü yine de
çalışıyor: `__getitem__` sıfırdan başlayarak artan dizinlerle **dört kez** çağrılıyor.
Yineleyicisi olmayan bir nesne için dil ikinci bir yineleme yolu tanır — sırayla dizin
iste, bir dizin geçersiz olana kadar.

İki rolü ayırmak gerekir. **Yinelenebilir** bir nesne `__iter__` tanımlayan nesnedir;
kendisi gezilmez, gezecek olanı verir. **Yineleyici** ise `__next__` tanımlayan nesnedir
ve asıl gezme işini o yapar. Bir nesnenin ikisini birden tanımlaması yasak değildir —
`Izci` tam olarak bunu yapar ve `__iter__` gövdesinde kendisini döndürür. Bu yüzden
`Izci` hem yinelenebilir hem yineleyicidir; ayrım kaybolduğunda ortaya çıkan sonuç aşağıda
ölçülüyor.

Sayı yine **dört**, öğe sayısından bir fazla. Ama fazladan çağrıyı bitiren istisna bu kez
başka: son satır `Dizinli()[3]` çağrısını doğrudan yapıyor ve çıkan şey `StopIteration`
değil, **`IndexError`**. İki protokol, iki ayrı bitiş istisnası, aynı **+1** düzeni. Her
iki yolda da döngünün bittiğini söyleyen şey bir dönüş değeri değil, fırlatılan bir
istisnadır.

#### Çarpım Kuralının Bozulduğu Yer

Dördüncü tablo iç içe iki döngüyü ölçüyor ve Programlama Temelleri kursunun iç içe
döngüler için verdiği kuralı — toplam tur sayısı ikisinin **çarpımıdır** — sınıyor. Ayrı
nesneler üzerinde kural tutuyor: dış **3** tur, iç **9** tur, yani üç kere üç.

Aynı nesne üzerinde tutmuyor. Dış döngü **3** değil **1** tur dönüyor; iç döngü **9**
değil **3**. Çarpım kuralı çöküyor ve bunun nedeni döngünün kendisi değil, `__iter__`'in
ne döndürdüğü.

`Izci.__iter__` yeni bir nesne üretmez; **kendisini** döndürür ve sayacı sıfırlar. Dış
döngü ilk öğesini aldıktan sonra iç döngü aynı nesne üzerinde `__iter__` çağırıyor, sayaç
sıfırlanıyor, iç döngü üç öğeyi de tüketip sayacı sona getiriyor. Dış döngü sıradaki öğeyi
istediğinde sayaç zaten sonda: `StopIteration` geliyor ve dış döngü birinci turunda
bitiyor. İki döngü **aynı sayacı** paylaşıyor.

Buradaki ayrım, yinelenebilir bir nesne ile yineleyicinin ayrı şeyler olmasıdır. Her
`__iter__` çağrısında **taze bir yineleyici** döndüren bir nesne iç içe döngülerde
beklendiği gibi davranır; kendisini döndüren bir nesne, aynı anda birden çok kez
gezilemez. Sözdizim ikisinde de aynıdır, tur sayısı değildir — ve hangi davranışın
geçerli olduğunu söyleyen tek şey `__iter__`'in gövdesidir. Nesnenin tipine, adına ya da
kaç öğe taşıdığına bakarak bu ayrımı görmek olanaksızdır.

Son satır `while` için de aynı düzeni veriyor. Üç öğe boşalana kadar dönen döngü
`__len__`'i **dört kez** çağırıyor: üç tur için üç sınama, bir de koşulu yanlışlayan
dördüncü sınama. `for` bir `__next__` fazla çağırır, `while` bir doğruluk sınaması fazla
yapar. Fazladan olan, her iki durumda da **döngüyü bitiren** sınamadır.

#### Özet

- Python'da döngü bir denetim yapısı değil, bir protokoldür: `for x in n` önce `__iter__`
  çağırır, sonra her tur için `__next__`; sınadığı bir koşul yoktur.
- Üç öğelik nesnede `__next__` **dört kez** çağrılır ve fark öğe sayısından bağımsızdır —
  sıfır öğede bile bir çağrı yapılır, çünkü boşluk ancak sorularak öğrenilir.
- Fazladan çağrı bir değer döndürmez, **`StopIteration` fırlatır**; döngüyü bitiren şey
  budur ve `for` bu istisnayı kendisi yakaladığı için görünmez. İstisna bir hata değil,
  protokolün parçasıdır.
- `for` yazımı `iter`, `next` ve `StopIteration` yakalamanın kısaltmasıdır; elle yazılan
  açılım birebir aynı çağrı dizisini üretir. `break` ise döngüyü protokolün dışından
  bitirir ve fazladan çağrıyı ortadan kaldırır — `else` bloğu tam olarak bu ayrımı okur.
- `__iter__` tanımlıyken `__getitem__` yinelemeye hiç katılmaz; yalnız `__getitem__`
  tanımlı bir nesnede döngü artan dizinlerle onu çağırır ve bu yolu bitiren istisna
  `IndexError`'dır.
- İç içe döngülerin çarpım kuralı, `__iter__` kendisini döndürdüğünde bozulur: aynı nesne
  üzerinde iki döngü tek sayacı paylaşır ve dış döngü **3** yerine **1** tur döner; ayrı
  nesnelerde kural yine tutar (**3** ve **9**).

#### Sonraki Adım

Döngü gövdesi çoğu zaman bir işlev çağırır ve o işleve her turda başka değerler geçer.
Programlama Temelleri kursu parametre ile argüman ayrımını ve çağrı çerçevesini kurmuştu;
Python'da argüman geçirmenin biçimleri o dersin varsaydığından fazladır — konumsal ve
anahtarlı argümanlar, değişken sayıda argüman toplama, varsayılan değerler. Bunların
içinde bir tanesi zamanlamayla ilgilidir ve sonraki dersin ölçtüğü şey odur: bir
parametrenin varsayılan değeri **ne zaman** hesaplanır — her çağrıda mı, yoksa bir kez mi?
Yanıt, değiştirilebilir bir varsayılanla birlikte şaşırtıcı bir sayı üretir.

### Fonksiyon Tanımı

Source: https://academia.sh/tr/kurslar/python-temelleri/fonksiyon-tanimi

Varsayılan değer her çağrıda değil, tanım okunduğunda bir kez değerlendirilir: beş çağrı bir tek değerlendirme yapar, değiştirilebilir bir varsayılan beş çağrıda beş öğe biriktirir ve altı parametrenin dördü tek bir yolla verilebilir.

Önceki ders döngünün bir protokol olduğunu ölçtü ve turların bitişine bir istisnanın karar
verdiğini gösterdi. Döngü gövdesi çoğu zaman bir işlev çağırır; her turda ona başka
değerler geçer.

Programlama Temelleri kursunun fonksiyon dersi parametre ile argüman ayrımını, dönüş
değerini ve çağrı çerçevesini kurmuştu; o ders **fonksiyonun ne olduğunu** anlatıyordu ve
argüman geçirmeyi tek bir soruya indirgemişti — hangi değer hangi parametreye gider.
Python'da bu sorunun yanıtı tek satırlık değildir: bir parametre yalnız konumundan, yalnız
adından ya da ikisinden de verilebilir; artan argümanlar toplanabilir; parametrelere
varsayılan değer bağlanabilir. Bunların içinde bir tanesi değer hakkında değil,
**zamanlama** hakkındadır ve bu dersin ölçtüğü şey odur: bir varsayılan değer **ne zaman**
hesaplanır — her çağrıda mı, yoksa bir kez mi?

#### Varsayılan Bir İfadedir

`def` bir deyimdir ve çalıştırıldığında bir fonksiyon nesnesi üretir. Üretim sırasında
gövde çalıştırılmaz — o ders kurulmuştu — ama **imzadaki varsayılan değer ifadeleri
çalıştırılır**. Sonuçları hesaplanır ve fonksiyon nesnesinin üzerine yazılır.

Bunun anlamı şudur: varsayılan değer, çağrının değil **tanımın** parçasıdır. Çağrı
sırasında yeniden hesaplanmaz; tanım okunduğunda bir kez hesaplanmış olan nesne her
çağrıda yeniden **bağlanır**. Beş çağrı, beş ayrı varsayılan üretmez; beş kez aynı
nesneyi kullanır.

"Bir kez" ifadesi programın ömrü boyunca bir kez demek değildir; **tanım deyimi her
çalıştığında bir kez** demektir. En dış düzeydeki bir tanım dosya okunurken bir kez
çalışır, dolayısıyla varsayılanı da bir kez hesaplanır. Başka bir fonksiyonun gövdesine
yazılmış bir tanım ise **dış fonksiyonun her çağrısında** yeniden çalışır ve varsayılanını
o çağrıya özel olarak yeniden hesaplar. Ölçülen kural değişmiyor — varsayılan hâlâ
tanımın parçası — ama tanımın kaç kez çalıştığı değişiyor.

Değer değiştirilemez bir nesneyse bu ayrım görünmez. Bir sayının ya da bir dizginin her
çağrıda aynı nesne olması hiçbir sonuç doğurmaz, çünkü onu değiştirmenin yolu yoktur.
Ayrım, varsayılan **değiştirilebilir** olduğunda ortaya çıkar: gövde onu değiştirirse,
değişiklik bir sonraki çağrıya taşınır.

#### Nöbetçi Değer

Bu davranışın karşısına konan kalıp, varsayılanı `None` yapıp gerçek değeri gövdede
üretmektir. `None` değiştirilemez, dolayısıyla paylaşılması bir sonuç doğurmaz; gövde her
çağrıda taze bir nesne kurar. Bu role **nöbetçi değer** denir: kendisi kullanılmaz, yalnız
"argüman verilmedi" bilgisini taşır.

Nöbetçinin `None` olması zorunlu değildir ama gerekçesi vardır. Bir parametrenin geçerli
değerleri arasında `None` da bulunabiliyorsa nöbetçi olarak `None` seçilemez — o zaman
"verilmedi" ile "`None` verildi" ayrılamaz. Bu durumda ayrı bir nesne nöbetçi yapılır.

#### Bir Argüman Kaç Yolla Verilir

İkinci ölçü, argüman biçimlerinin sayısıdır. Python imzada iki ayraç tanır. Eğik çizgi,
solundaki parametrelerin **yalnız konumsal argümanla** verilebileceğini bildirir. Yıldız,
sağındaki parametrelerin **yalnız anahtarlı argümanla** verilebileceğini bildirir. İkisinin
arasında kalanlar her iki yolla da verilebilir.

Buna iki toplayıcı eklenir. Yıldızlı bir parametre, karşılanmamış **konumsal** argümanları
bir demette toplar; çift yıldızlı bir parametre, karşılanmamış **anahtarlı** argümanları
bir sözlükte toplar. İkisine birlikte **değişken sayıda argüman** denir ve imzanın kaç
argüman alabileceğini üst sınırdan kurtarır.

İmzadaki bu parçaların sırası serbest değildir: önce yalnız konumsal olanlar, sonra her
iki yolla verilebilenler, sonra konumsal toplayıcı, sonra yalnız anahtarlı olanlar, en
sonda anahtarlı toplayıcı gelir. Sıra, bağlanmayı **tek anlamlı** kılmak için vardır.
Çağrıdaki argümanlar soldan sağa okunur; konumsal olanlar imzadaki konumsal parametreleri
sırayla doldurur, dolduramadıkları toplayıcıya girer, anahtarlı olanlar adlarıyla
eşleşir, eşleşemeyenler anahtarlı toplayıcıya düşer. Bu sıra bozulsaydı aynı çağrının
birden çok geçerli bağlanması olurdu ve hangisinin seçileceği yazılana bakılarak
söylenemezdi.

Aynı gerekçe varsayılan taşıyan parametrelerin taşımayanlardan sonra gelmesini de
zorunlu kılar — konumsal tarafta. Yalnız anahtarlı tarafta bu kısıt yoktur: orada
eşleşme ada göre yapıldığı için varsayılanı olan ve olmayan parametreler karışık
sırayla yazılabilir.

Ölçümün varsayımları:

- **PA16** — Kâhin kurgunun kendisidir: varsayılan ifadesi bir işlev çağrısıdır ve her
  değerlendirilişini kaydeder; "kaç kez değerlendirildi" sorusunun ölçüsü bu kayıttır.
- **PA17** — Değerlendirme sayısı iki noktada okunur: tanım deyimi çalıştıktan hemen
  sonra ve beş çağrı yapıldıktan sonra. İkisinin farkı, çağrıların kattığı
  değerlendirmedir.
- **PA18** — İki fonksiyon aynı işi yapar ve yalnız varsayılanında ayrılır: biri boş bir
  liste, öbürü `None` nöbetçisi. Gövdelerin geri kalanı birebir aynıdır.
- **PA19** — Paylaşım, art arda iki çağrının döndürdüğü nesne `is` ile karşılaştırılarak
  gösterilir; **hiçbir kimlik sayısı basılmaz**. Karşılaştırılan nesne bir listedir;
  değiştirilemez küçük değerlerin önbelleklenmesi ölçüme karışmaz.
- **PA20** — İmza altı parametre ve iki toplayıcı taşır; sayılar bu imzaya özgüdür ve
  başka bir imzaya taşınmaz.
- **PA21** — Bağlanamayan çağrılarda yalnız **istisnanın sınıfı** yazılır, iletisi
  yazılmaz; ileti metni ölçünün parçası değildir.
- **PA22** — Varsayılanın nerede durduğu, fonksiyon nesnesinin varsayılanları taşıyan
  alanı okunarak gösterilir; okunan şey bir bellek bilgisi değil, dilin kendi tanımladığı
  bir alandır.
- **PA23** — Çağrı yerindeki açma biçimi, imzadaki toplayıcılarla aynı ayraçları kullanır
  ama ters yönde çalışır; ölçümde ikisi aynı imza üzerinde karşılaştırılır.

#### Ölçüm

```python
"""Varsayilan deger ne zaman degerlendirilir ve argumanlar nasil baglanir."""

DEGERLENDIRME = []


def damga(ad):
    """Her cagrildiginda kaydeder; varsayilan ifadesi olarak kullanilir."""
    DEGERLENDIRME.append(ad)
    return len(DEGERLENDIRME)


def sirala(x, d=damga("varsayılan ifade")):
    return d


print(f"tanımdan hemen sonra değerlendirme: {len(DEGERLENDIRME)}")
sonuclar = [sirala(i) for i in range(5)]
print(f"beş çağrıdan sonra değerlendirme: {len(DEGERLENDIRME)}")
print(f"beş çağrının döndürdüğü: {sonuclar}")


def biriktir(x, kova=[]):
    kova.append(x)
    return kova


def nobetci(x, kova=None):
    if kova is None:
        kova = []
    kova.append(x)
    return kova


print()
print(f"{'çağrı':>6s} {'kova=[] uzunluk':>16s} {'kova=None uzunluk':>18s}")
onceki_a = onceki_b = None
for i in range(1, 6):
    a, b = biriktir(i), nobetci(i)
    print(f"  {i:4d} {len(a):16d} {len(b):18d}")
    if i > 1:
        ayni_a, ayni_b = a is onceki_a, b is onceki_b
    onceki_a, onceki_b = a, b
print(f"art arda iki çağrının kovası aynı nesne mi: "
      f"kova=[] {ayni_a}, kova=None {ayni_b}")
print(f"fonksiyon nesnesinde duran varsayılan: "
      f"biriktir {biriktir.__defaults__}, nobetci {nobetci.__defaults__}")
print(f"dönen liste, fonksiyonda duran varsayılanın kendisi mi: "
      f"{biriktir(6) is biriktir.__defaults__[0]}")


def kaydol(a, b, /, c, d=40, *ek, e, f=60, **kalan):
    """a,b yalnız konumsal; e,f yalnız anahtarlı; ek ve kalan artanı toplar."""
    return {"a": a, "b": b, "c": c, "d": d, "ek": ek, "e": e, "f": f,
            "kalan": kalan}


CAGRILAR = (
    "kaydol(1, 2, 3, e=5)",
    "kaydol(1, 2, c=3, e=5)",
    "kaydol(1, 2, 3, 4, 7, 8, e=5, f=9, g=0)",
    "kaydol(1, 2, 3)",
    "kaydol(a=1, b=2, c=3, e=5)",
)

print()
print(f"{'çağrı':<42s} bağlanma")
for metin in CAGRILAR:
    try:
        s = eval(metin)
        b = (f"a={s['a']} b={s['b']} c={s['c']} d={s['d']} ek={s['ek']} "
             f"e={s['e']} f={s['f']} kalan={s['kalan']}")
    except TypeError as hata:
        b = type(hata).__name__
    print(f"  {metin:<40s} {b}")

KONUMSAL = (1, 2, 3)
ANAHTARLI = {"e": 5, "g": 0}
acilan = kaydol(*KONUMSAL, **ANAHTARLI)
print(f"  {'kaydol(*KONUMSAL, **ANAHTARLI)':<40s} "
      f"a={acilan['a']} b={acilan['b']} c={acilan['c']} d={acilan['d']} "
      f"ek={acilan['ek']} e={acilan['e']} f={acilan['f']} "
      f"kalan={acilan['kalan']}")

print()
print("parametre  konumsal verilebilir  anahtarlı verilebilir")
for ad, konumsal, anahtarli in (("a", True, False), ("b", True, False),
                                ("c", True, True), ("d", True, True),
                                ("e", False, True), ("f", False, True)):
    print(f"  {ad:<8s} {('evet' if konumsal else 'hayır'):>18s}"
          f" {('evet' if anahtarli else 'hayır'):>21s}")
```

```
tanımdan hemen sonra değerlendirme: 1
beş çağrıdan sonra değerlendirme: 1
beş çağrının döndürdüğü: [1, 1, 1, 1, 1]

 çağrı  kova=[] uzunluk  kova=None uzunluk
     1                1                  1
     2                2                  1
     3                3                  1
     4                4                  1
     5                5                  1
art arda iki çağrının kovası aynı nesne mi: kova=[] True, kova=None False
fonksiyon nesnesinde duran varsayılan: biriktir ([1, 2, 3, 4, 5],), nobetci (None,)
dönen liste, fonksiyonda duran varsayılanın kendisi mi: True

çağrı                                      bağlanma
  kaydol(1, 2, 3, e=5)                     a=1 b=2 c=3 d=40 ek=() e=5 f=60 kalan={}
  kaydol(1, 2, c=3, e=5)                   a=1 b=2 c=3 d=40 ek=() e=5 f=60 kalan={}
  kaydol(1, 2, 3, 4, 7, 8, e=5, f=9, g=0)  a=1 b=2 c=3 d=4 ek=(7, 8) e=5 f=9 kalan={'g': 0}
  kaydol(1, 2, 3)                          TypeError
  kaydol(a=1, b=2, c=3, e=5)               TypeError
  kaydol(*KONUMSAL, **ANAHTARLI)           a=1 b=2 c=3 d=40 ek=() e=5 f=60 kalan={'g': 0}

parametre  konumsal verilebilir  anahtarlı verilebilir
  a                      evet                 hayır
  b                      evet                 hayır
  c                      evet                  evet
  d                      evet                  evet
  e                     hayır                  evet
  f                     hayır                  evet
```

#### Bir Kez Değerlendirilen İfade

İlk üç satır zamanlama sorusunu kapatıyor. Tanım deyimi çalıştıktan hemen sonra, **henüz
hiçbir çağrı yapılmadan**, değerlendirme sayısı **1**. Varsayılan ifadesi tanım okunurken
çalışmış.

Beş çağrıdan sonra sayı yine **1**. Çağrılar hiçbir değerlendirme eklemedi. Üçüncü satır
bunu doğrudan gösteriyor: beş çağrının beşi de aynı değeri döndürüyor — hepsi tanım
sırasında hesaplanmış tek nesneyi görüyor.

Bir fonksiyonun çağrılma sayısı ile varsayılanının değerlendirilme sayısı arasında
**bağlantı yoktur**. Bu, imzada yazılan ifadenin gövdede yazılmış gibi okunmasının neden
yanlış olduğunu açıklar. `def` satırındaki bir ifade, gövdedeki bir ifadeyle aynı yerde
durmuyor: biri bir kez, öbürü her çağrıda çalışıyor.

#### Paylaşılan Kova

Orta tablo bu tek değerlendirmenin bedelini ödetiyor. Sol sütunda varsayılan boş bir
liste; beş çağrı boyunca uzunluk **1, 2, 3, 4, 5** diye büyüyor. Her çağrı bir öncekinin
bıraktığı listeyi buluyor.

Sağ sütunda nöbetçi kalıbı; uzunluk beş çağrıda da **1**. Her çağrı kendi listesini
kuruyor ve bir öncekinden hiçbir şey devralmıyor.

Alt satır nedeni kimlik üzerinden söylüyor. `kova=[]` biçiminde art arda iki çağrının
döndürdüğü nesne `is` sınamasından **doğru** çıkıyor: aynı nesne. Nöbetçi kalıbında
**yanlış**: ayrı nesneler. Fonksiyonun her çağrıda "yeni bir liste" aldığı sanısı burada
kırılıyor — liste tanım sırasında bir kez kurulmuş, fonksiyon nesnesine bağlanmış ve o
tarihten sonra her çağrının değiştirdiği tek bir nesne olarak yaşıyor.

Sondan bir önceki satır varsayılanın **nerede durduğunu** gösteriyor ve bu, dersin en
doğrudan kanıtıdır. Fonksiyon nesnesinin varsayılanları taşıyan alanı okunduğunda içinde
`[1, 2, 3, 4, 5]` duruyor — beş çağrının biriktirdiği liste, çağrılar bittikten sonra
**fonksiyonun kendisinde**. Nöbetçi kalıbında aynı alanda yalnız `None` var; orada
biriken hiçbir şey yok, çünkü biriken listeler gövdede kurulup çağrıyla birlikte
bırakıldı.

Son satır halkayı kapatıyor: bir çağrının döndürdüğü liste ile fonksiyon nesnesinde duran
varsayılan `is` sınamasından **doğru** çıkıyor. İkisi tek bir nesne. "Varsayılan tanımın
parçasıdır" cümlesi burada bir yorum olmaktan çıkıyor; fonksiyonun üzerinde durduğu
okunabiliyor.

Sonucun kendisi bir hata değil, kuralın doğrudan sonucudur. Programlama Temelleri kursu
bu tuzağı bir uyarı olarak yazmıştı; buradaki sayılar uyarının gerekçesini veriyor.
Kuralı bilen için davranış şaşırtıcı değildir: gövde varsayılanı değiştiriyorsa,
değiştirdiği şey **tanımın parçasıdır**.

#### Altı Parametre, İki Ayraç

Alt iki tablo bağlanmayı ölçüyor. İlk iki çağrı aynı sonucu veriyor: `c` bir kez konumdan,
bir kez adından veriliyor ve bağlanma birebir aynı. `d`, `e` ve `f` verilmediği için `d`
ile `f` varsayılanlarına düşüyor, `e` ise çağrıda mutlaka yazılmak zorunda — çünkü
varsayılanı yok ve yalnız anahtarlı verilebiliyor.

Üçüncü çağrı iki toplayıcıyı da dolduruyor. Altı konumsal argüman veriliyor; ilk dördü
`a`, `b`, `c`, `d` parametrelerini karşılıyor, artan **iki tanesi** `ek` demetine
giriyor. Anahtarlı tarafta `e` ile `f` kendi parametrelerini buluyor, imzada karşılığı
olmayan `g` ise `kalan` sözlüğüne düşüyor. Toplayıcılar, bağlanamayan argümanı hataya
dönüştürmek yerine **toplayan** parametrelerdir.

Son iki çağrı bağlanamıyor ve ikisi de `TypeError` veriyor. Dördüncü çağrıda `e` eksik:
varsayılanı olmayan, yalnız anahtarlı bir parametre yazılmadan çağrı kurulamaz. Beşinci
çağrıda tam tersi olmuş — `a` ile `b` adlarıyla verilmiş, oysa eğik çizginin solundalar ve
yalnız konumdan alınabilirler. Adlarıyla verilen `a` ile `b`, parametreleri karşılamak
yerine `kalan` sözlüğüne düşecek adlar olarak görülüyor ve iki konumsal parametre boş
kalıyor.

Altıncı satır aynı ayraçların **çağrı yerinde** ne yaptığını gösteriyor. İmzada yıldız
argümanları **toplarken**, çağrıda tam tersini yapar: bir demeti konumsal argümanlara,
bir sözlüğü anahtarlı argümanlara **açar**. Üç öğelik demet `a`, `b`, `c` parametrelerini
karşılıyor; sözlükteki `e` kendi parametresini buluyor, `g` ise `kalan` sözlüğüne düşüyor.

İki yönün aynı ayraçları kullanması bir rastlantı değil: toplama ile açma birbirinin
tersidir ve bir fonksiyonun aldığı argümanları başka bir fonksiyona olduğu gibi
geçirmenin yolu ikisini yan yana kullanmaktır. Bu, sarmalayan fonksiyon yazmanın temel
kalıbıdır — sarmalayan, imzayı bilmeden argümanları toplar ve sarmaladığı fonksiyona açar.

Son tablo imzanın tümünü özetliyor. Altı parametrenin **dördü tek bir yolla** verilebilir
— `a` ile `b` yalnız konumdan, `e` ile `f` yalnız addan. Yalnız **ikisi** her iki yolu da
kabul ediyor. Ayraçların işi budur: bir parametrenin kaç yolla verilebileceğini daraltmak.
Daraltmanın karşılığı, çağrı yazımının imzayla birlikte değişmemesidir — konumsal
tutulan bir parametrenin adı değiştiğinde hiçbir çağrı bozulmaz, anahtarlı tutulan bir
parametrenin sırası değiştiğinde de öyle.

#### Özet

- Varsayılan değer ifadesi çağrının değil **tanımın** parçasıdır: tanım okunurken bir kez
  değerlendirilir ve beş çağrı bu sayıyı değiştirmez — çağrı sayısıyla değerlendirme
  sayısı arasında bağlantı yoktur.
- Değiştirilemez bir varsayılanda bu görünmez; **değiştirilebilir** bir varsayılanda gövde
  tanımın parçasını değiştirir ve beş çağrı tek bir listede **beş öğe** biriktirir.
- `None` nöbetçisi kalıbı taze nesneyi gövdede kurar; art arda iki çağrının kovası `is`
  sınamasından ayrı nesneler olarak çıkar ve uzunluk her çağrıda **1** kalır.
- Eğik çizgi solundaki parametreleri yalnız konumsal, yıldız sağındakileri yalnız
  anahtarlı yapar; ölçülen imzada altı parametrenin **dördü tek yolla**, ikisi iki yolla
  verilebilir.
- Yıldızlı ve çift yıldızlı toplayıcılar karşılanmamış konumsal ve anahtarlı argümanları
  toplar; aynı ayraçlar çağrı yerinde ters yönde çalışır ve bir demetle bir sözlüğü
  argümanlara **açar**. Toplanamayan argüman ile eksik kalan parametre `TypeError` üretir.
- Varsayılan, fonksiyon nesnesinin kendi alanında durur: beş çağrıdan sonra o alanda
  biriken listenin tamamı okunabilir ve dönen liste `is` sınamasıyla onunla aynı nesne
  çıkar.

#### Sonraki Adım

Fonksiyon gövdesi, argümanlarla gelen adların yanında başka adlar da kullanır: gövdede
kurulanlar, onu çevreleyen tanımdan gelenler, dosyanın en dışında duranlar ve dilin kendi
sağladıkları. Programlama Temelleri kursu bu arama sırasını dilden bağımsız bir kavram
olarak kurmuştu. Python'da sıra **dört düzeylidir** ve düzeylerden hangisinde bulunacağını
belirleyen şey adın nerede **okunduğu** değil, nerede **atandığıdır**. Sonraki ders aynı
adı dört düzeyde birden tanımlayıp hangisinin kazandığını ölçer — ve gövdeye tek bir atama
satırı eklemenin aynı adı nasıl başka bir düzeye taşıdığını.

### Kapsam Kuralları

Source: https://academia.sh/tr/kurslar/python-temelleri/kapsam-kurallari

Ad araması dört düzeyde yürür ve hangi düzeyde durduğunu adın okunduğu yer değil, atandığı yer belirler: gövdeye tek bir atama satırı eklemek aynı okumayı geçerli bir değerden UnboundLocalError'a çevirir.

Önceki ders argümanların parametrelere nasıl bağlandığını ölçtü. Bağlanan her argüman
gövdede bir **ad** kurar; ama gövde yalnız o adları kullanmaz. Kendi kurduğu adlar,
çevreleyen tanımdan gelenler, dosyanın en dışında duranlar ve dilin kendi sağladıkları da
oradadır — ve hepsi aynı yazımla, çıplak bir adla okunur.

Programlama Temelleri kursunun kapsam dersi bu arama sırasını dilden bağımsız bir **kavram**
olarak kurdu: yerelden başlanır, çevreleyene ve küresele gidilir, yerleşiklerle biter, ilk
bulunan kazanır. O ders kapsamı görünürlükle, yaşam süresini ömürle tanımladı ve
gölgelemenin ne olduğunu anlattı. Burada aynı sıra bir **arama zinciri** olarak ölçülür ve
ölçüm bir soruya odaklanır: bir adın hangi düzeyde aranacağını belirleyen şey nedir?
Yanıt sezgiye aykırıdır — belirleyen şey adın **okunduğu** yer değil, gövdede bir yerde
**atanıp atanmadığıdır**.

#### Dört Düzey

Python'da ad araması dört düzeyde yürür ve sırası sabittir:

1. **Yerel** — içinde bulunulan fonksiyon gövdesi.
2. **Kapsayan** — onu çevreleyen fonksiyon gövdeleri, içten dışa.
3. **Genel** — dosyanın en dış düzeyi.
4. **Yerleşik** — dilin kendi sağladığı adlar.

Arama ilk bulduğu yerde durur ve alttaki düzeylere hiç bakmaz. Dördü de bulamazsa sonuç
bir değer değil, `NameError` olur.

Dördüncü düzey öbürlerinden farklıdır: içindeki adları program yazmaz, dil koyar. Ama
arama sırasındaki yeri özel değildir — en sonda olduğu için, üstteki üç düzeyden herhangi
biri aynı adı tanımlarsa yerleşik olan **gölgelenir** ve o gövdede erişilemez olur.

#### Adı Yerel Yapan Şey

Asıl kural buradadır ve gövdenin tamamını ilgilendirir. Python bir gövdedeki adın hangi
düzeye ait olduğuna **çalışma sırasında değil, gövdeyi okurken** karar verir. Kural tek
cümledir: **gövdede bir yerde ada atama yapılıyorsa, o ad o gövdenin tamamı için
yereldir.**

"Bir yerde" ifadesi harfiyen alınmalıdır. Atama gövdenin en son satırında olsa bile ad
gövdenin en başından itibaren yereldir. Bu yüzden atamadan **önce** yapılan bir okuma,
dıştaki değeri bulmaz — henüz değer almamış yerel adı bulur ve sonuç bir istisna olur:
`UnboundLocalError`.

Bu, gölgelemenin ölçülebilir hâlidir. Programlama Temelleri kursu gölgelemeyi "dıştaki ad
erişilemez hâle gelir" diye tanımlamıştı; buradaki ölçüm erişilemezliğin **nereden
başladığını** söylüyor — atama satırından değil, gövdenin ilk satırından.

#### Dışarı Yazmak

Kuralın doğrudan sonucu şudur: dıştaki bir ada **atama yapmak** çıplak yazımla olanaksızdır.
Atama her zaman yerel bir ad kurar; dıştakini değiştirmez, onu gölgeler.

Dil bu yüzden iki bildirim tanır. `global`, adın genel düzeye ait olduğunu bildirir ve o
gövdedeki atamaları oraya yönlendirir. `nonlocal`, adın **kapsayan** bir gövdeye ait
olduğunu bildirir ve atamaları oraya yönlendirir; genel düzeye bakmaz, bulamazsa hata
verir.

Okumak için bildirim gerekmez. Bildirim yalnız **yazma** yönünü değiştirir; kural yazma
kuralıdır, okuma kuralı değil. Bu asimetri kasıtlıdır: dıştaki bir değeri okumak o değeri
kimse için değiştirmez, ama ona yazmak gövdenin dışında görülebilir bir sonuç bırakır.
Bildirim zorunluluğu, o sonucun kazara doğmasını engeller — dışarı yazan her gövde bunu
tek satırla ilan etmek zorundadır.

Ölçümün varsayımları:

- **PA24** — Kâhin, her düzeyin o düzeyi adlandıran ayrı bir dizgi bağlamasıdır; dönen
  değer, aramanın hangi düzeyde durduğunu doğrudan söyler.
- **PA25** — Aynı ad `SINIR` dört durumda ölçülür ve durumlar yalnız **hangi düzeylerin
  o adı tanımladığı** bakımından ayrılır; gövdelerin okuma satırı birebir aynıdır.
- **PA26** — Yerleşik düzey ayrı bir adla ölçülür, çünkü o düzeydeki adları program
  yazamaz. Ölçüm için dilin sağladığı `min` adı kullanılır ve üstteki düzeylerde
  gölgelenir.
- **PA27** — Atama kuralı üç gövdeyle ölçülür: yalnız okuyan, okuyup sonra atayan, yalnız
  atayan. Üçünde de okuma satırı aynıdır; değişen tek şey gövdede bir atamanın **bulunup
  bulunmadığı** ve yeridir.
- **PA28** — Yazma yönü üç durumda ölçülür: bildirimsiz, `nonlocal` bildirimiyle ve
  `global` bildirimiyle. Genel düzeydeki ad ölçümün sonunda değiştiği için o satır en
  sonda çalıştırılır.
- **PA29** — İstisnalar yalnız **sınıf adıyla** yazılır, iletileri yazılmaz.
- **PA30** — Kapanış ölçümünde üç işlev bir döngüde üretilir ve **döngü bittikten sonra**
  çağrılır. Çağrının döngüden sonraya bırakılması kasıtlıdır; ölçülen şey adın **ne zaman
  çözüldüğüdür**, hangi değeri taşıdığı değil.

#### Ölçüm

```python
"""Dort kapsam duzeyi: ad hangi duzeyde bulunur, atama onu nereye tasir."""

SINIR = "genel"


def olc(islev):
    try:
        return islev()
    except Exception as e:
        return f"!{type(e).__name__}"


def hicbiri():
    return TANIMSIZ_AD


def yalniz_genel():
    return SINIR


def kapsayan_da_var():
    SINIR = "kapsayan"

    def ic():
        return SINIR
    return ic()


def yerel_de_var():
    SINIR = "kapsayan"

    def ic():
        SINIR = "yerel"
        return SINIR
    return ic()


ARAMA = (
    ("hiçbir düzeyde yok", hicbiri, "-"),
    ("yalnız genel", yalniz_genel, "genel"),
    ("genel + kapsayan", kapsayan_da_var, "genel, kapsayan"),
    ("genel + kapsayan + yerel", yerel_de_var, "genel, kapsayan, yerel"),
)

print(f"{'adın tanımlı olduğu düzeyler':<26s} {'bulunan':<22s} sonuç")
for ad, f, tanimli in ARAMA:
    print(f"  {ad:<24s} {tanimli:<22s} {olc(f)}")


def yerlesigi_kullanir():
    return min([3, 1, 2])


def yerlesigi_kapsayan_golgeler():
    min = "kapsayan gölgeledi"

    def ic():
        return min
    return ic()


def yerlesigi_yerel_golgeler():
    def ic():
        min = "yerel gölgeledi"
        return min
    return ic()


print()
print(f"{'yerleşik ad min':<30s} sonuç")
for ad, f in (("hiçbir düzey tanımlamaz", yerlesigi_kullanir),
              ("kapsayan düzey tanımlar", yerlesigi_kapsayan_golgeler),
              ("yerel düzey tanımlar", yerlesigi_yerel_golgeler)):
    print(f"  {ad:<28s} {olc(f)}")


def yalniz_okur():
    return SINIR


def okur_sonra_atar():
    once = SINIR
    SINIR = "yerel"
    return once


def yalniz_atar():
    SINIR = "yerel"
    return SINIR


print()
print(f"{'gövdede SINIR ile yapılan':<30s} {'gövdedeki okuma':<18s} sonuç")
for ad, f, okuma in (("yalnız okunur", yalniz_okur, "atamadan önce yok"),
                     ("okunur, sonra atanır", okur_sonra_atar, "atamadan önce"),
                     ("yalnız atanır", yalniz_atar, "atamadan sonra")):
    print(f"  {ad:<28s} {okuma:<18s} {olc(f)}")


def genel_bildirir():
    global SINIR
    SINIR = "içeriden yazıldı"


def kapsayan_bildirir():
    SINIR = "kapsayan"

    def ic():
        nonlocal SINIR
        SINIR = "içeriden yazıldı"
    ic()
    return SINIR


def bildirimsiz_yazar():
    SINIR = "kapsayan"

    def ic():
        SINIR = "içeriden yazıldı"
    ic()
    return SINIR


print()
print(f"{'gövdeden dışarı yazma':<26s} {'hedef düzey':<12s} dıştaki adın sonraki değeri")
for ad, f, hedef in (("bildirim yok", bildirimsiz_yazar, "kapsayan"),
                     ("nonlocal bildirimi", kapsayan_bildirir, "kapsayan")):
    print(f"  {ad:<24s} {hedef:<12s} {olc(f)}")

once = SINIR
genel_bildirir()
print(f"  {'global bildirimi':<24s} {'genel':<12s} {once} -> {SINIR}")


def gec_baglanan():
    islevler = []
    for i in range(3):
        def uret():
            return i
        islevler.append(uret)
    return [f() for f in islevler]


def varsayilanla_yakalanan():
    islevler = []
    for i in range(3):
        def uret(i=i):
            return i
        islevler.append(uret)
    return [f() for f in islevler]


def dongu_sonrasi_ad():
    for i in range(3):
        pass
    return i


print()
print(f"{'döngüde üretilen üç işlev':<30s} çağrıldıklarında dönen")
print(f"  {'gövdede çıplak ad':<28s} {gec_baglanan()}")
print(f"  {'varsayılanla yakalanmış ad':<28s} {varsayilanla_yakalanan()}")
print(f"  {'döngü bittikten sonra ad':<28s} {dongu_sonrasi_ad()}")
```

```
adın tanımlı olduğu düzeyler bulunan                sonuç
  hiçbir düzeyde yok       -                      !NameError
  yalnız genel             genel                  genel
  genel + kapsayan         genel, kapsayan        kapsayan
  genel + kapsayan + yerel genel, kapsayan, yerel yerel

yerleşik ad min                sonuç
  hiçbir düzey tanımlamaz      1
  kapsayan düzey tanımlar      kapsayan gölgeledi
  yerel düzey tanımlar         yerel gölgeledi

gövdede SINIR ile yapılan      gövdedeki okuma    sonuç
  yalnız okunur                atamadan önce yok  genel
  okunur, sonra atanır         atamadan önce      !UnboundLocalError
  yalnız atanır                atamadan sonra     yerel

gövdeden dışarı yazma      hedef düzey  dıştaki adın sonraki değeri
  bildirim yok             kapsayan     kapsayan
  nonlocal bildirimi       kapsayan     içeriden yazıldı
  global bildirimi         genel        genel -> içeriden yazıldı

döngüde üretilen üç işlev      çağrıldıklarında dönen
  gövdede çıplak ad            [2, 2, 2]
  varsayılanla yakalanmış ad   [0, 1, 2]
  döngü bittikten sonra ad     2
```

#### Zincirin Dört Basamağı

Üst tablo arama zincirini basamak basamak kuruyor. Dört satırın **okuma satırı birebir
aynı**; değişen tek şey hangi düzeylerin o adı tanımladığı.

Hiçbir düzeyde tanımlı olmadığında sonuç `NameError`. İkinci satırda ad yalnız genel
düzeyde var ve bulunan o. Üçüncü satırda kapsayan düzey de aynı adı tanımlıyor ve
**kapsayan kazanıyor** — genel düzeydeki ad hâlâ orada duruyor ama aramaya hiç
gelmiyor. Dördüncüde yerel düzey de ekleniyor ve bu kez **yerel kazanıyor**.

Örüntü şudur: her yeni düzey, kendinden **dıştaki** her şeyi kapatıyor. Arama dıştan içe
değil, **içten dışa** yürüyor ve ilk bulduğunda duruyor. Üç düzey aynı adı tanımladığında
üçü yarışmıyor; en içteki öbür ikisini sınamaya bile sokmuyor.

Orta tablo dördüncü düzeyi ekliyor. İlk satırda hiçbir düzey `min` adını tanımlamıyor ve
arama yerleşik düzeye kadar inip dilin sağladığı adı buluyor; sonuç, üç öğelik listenin
en küçüğü olan **1**. Sonraki iki satırda aynı ad önce kapsayan, sonra yerel düzeyde
tanımlanıyor ve ikisinde de yerleşik olan gölgeleniyor. Yerleşik düzeyin arama sırasında
ayrıcalığı yok; en sonda olduğu için **her zaman en kolay gölgelenen** düzey.

Bunun pratik sonucu, bir yerleşiğin adını yerel bir değişkene vermenin o gövdede o
yerleşiği kullanılamaz kılmasıdır. Hata çıkmaz, uyarı çıkmaz; ad artık başka bir şeyi
gösterir.

#### Tek Bir Atama Satırı

Üçüncü tablo dersin ana ölçümüdür ve üç satırın **okuma satırı yine aynıdır**.

Birinci satır: gövde `SINIR` adını yalnız okuyor, hiçbir yerde atamıyor. Arama zinciri
yürüyor, genel düzeyde bulunuyor, sonuç **genel**.

İkinci satır: gövde önce aynı okumayı yapıyor, **sonra** ada bir atama yapıyor. Okuma
satırı atamadan öncedir ve birinci satırdakiyle karakter karakter aynıdır. Ama sonuç
artık bir değer değil, **`UnboundLocalError`**.

Aradaki tek fark, gövdenin **ilerideki** bir satırında bir atamanın bulunmasıdır. O atama
adı gövdenin tamamı için yerel yapıyor; okuma satırına gelindiğinde arama artık genel
düzeye hiç çıkmıyor, yerel düzeyde duruyor ve orada henüz değer almamış bir ad buluyor.
Zincir kırılmıyor — **hiç kurulmuyor**.

Buradaki sıra ilişkisi kritiktir: adın yerel olmasına karar veren şey çalışma sırası
değil, gövdenin **tümüne bakılarak** verilen bir karardır. Bu yüzden hatayı üreten satır,
hatanın nedenini içermez; neden aşağıdaki atama satırıdır. Üçüncü satır bunu tamamlıyor —
gövde yalnız atayıp sonra okuduğunda sorun yok, sonuç **yerel**.

#### Yazmanın Üç Yolu

Alt tablo yazma yönünü ölçüyor. Birinci satırda iç gövde bildirimsiz bir atama yapıyor ve
dıştaki ad **değişmiyor**: sonuç hâlâ `kapsayan`. Atama dıştakini güncellemedi, iç gövdeye
ait yeni bir ad kurdu ve o ad gövde bitince ortadan kalktı. Yazılan satır bir güncelleme
gibi görünüyor; yaptığı iş bir gölgeleme.

İkinci satırda aynı gövde `nonlocal` bildirimiyle yazıyor ve dıştaki ad **değişiyor**.
Üçüncü satırda `global` bildirimi genel düzeydeki adı değiştiriyor; ölçümden önce `genel`
olan değer sonrasında `içeriden yazıldı` oluyor.

Üç satırın söylediği şudur: gövdeden dışarı yazmak **bildirimle** olur, çıplak atamayla
olmaz. Bildirim yeni bir yetenek eklemiyor; yalnız atamanın hangi düzeyi hedeflediğini
söylüyor. Bildirim olmadığında hedef her zaman yereldir ve bu, önceki tablodaki
`UnboundLocalError` ile aynı kuralın öbür yüzüdür — biri okumada, öbürü yazmada görünür.

Bu ayrım, dıştaki bir nesneyi **değiştirmekle** dıştaki adı **yeniden bağlamak**
arasındaki farkı da açıklar. Önceki dersteki paylaşılan kova hiçbir bildirim olmadan
büyüyordu, çünkü orada yapılan şey ada atama değil, adın gösterdiği nesneyi değiştirmekti.
Kapsam kuralları adların bağlanmasını yönetir; nesnelerin içeriğini değil.

#### Adın Ne Zaman Çözüldüğü

Son tablo zincirin **ne zaman** yürüdüğünü ölçüyor ve buraya kadarki her şeyi tek bir
sonuçta topluyor. Bir döngüde üç işlev üretiliyor; her biri gövdesinde döngü değişkenini
çıplak bir adla okuyor. Döngü bittikten sonra üçü de çağrılıyor.

Beklenen sonuç `[0, 1, 2]`, ölçülen sonuç **`[2, 2, 2]`**. Üç işlev de aynı değeri
döndürüyor.

Nedeni kapsam kuralının kendisidir. İşlevlerin gövdesindeki ad, işlev **üretildiğinde**
çözülmüyor; **çağrıldığında** çözülüyor. Üretim sırasında gövdeye yazılan şey bir değer
değil, bir addır — ve o ad kapsayan gövdedeki tek bir değişkeni gösterir. Üç işlev üç ayrı
değeri değil, **aynı adı** taşıyor. Çağrıldıklarında arama zinciri yürüyor, kapsayan
düzeydeki adı buluyor ve orada döngünün bıraktığı son değer duruyor.

Üçüncü satır o son değerin ne olduğunu gösteriyor: döngü bittikten sonra da ad yaşıyor ve
değeri **2**. Python'da döngü kendi kapsamını açmaz; döngü değişkeni onu çevreleyen
gövdeye aittir ve döngü bitince ortadan kalkmaz. Programlama Temelleri kursu döngü
değişkeninin **yerel** olduğunu söylemişti ve bu doğrudur — ölçüm onu daraltıyor: yerel
olduğu birim döngü değil, **fonksiyon gövdesinin tamamıdır**. Süslü parantezle ya da
anahtar sözcükle kapatılan blokların kendi kapsamını açtığı dillerde bu satır bir hata
verirdi; burada bir değer veriyor.

İkinci satır çözümü veriyor ve çözüm önceki dersin kuralıdır. Ad varsayılan değer olarak
yazıldığında sonuç **`[0, 1, 2]`** oluyor. Varsayılan ifadesi **tanım okunurken** bir kez
değerlendirilir; tanım burada döngü gövdesinde olduğu için her turda yeniden çalışıyor ve
o turdaki değeri yakalıyor. Değer, arama zincirine bırakılmak yerine tanım anında
kopyalanmış oluyor.

İki dersin kuralı burada birleşiyor: kapsam adın **nerede** aranacağını söylüyor,
varsayılanın değerlendirilme zamanı ise **ne zaman** aranacağını değiştiriyor. Aynı üç
satırlık döngü, tek bir imza değişikliğiyle iki ayrı sonuç veriyor.

#### Özet

- Ad araması dört düzeyde ve sabit bir sırada yürür — yerel, kapsayan, genel, yerleşik —
  ilk bulduğunda durur ve dördü de bulamazsa `NameError` verir.
- Her düzey kendinden dıştakileri kapatır: aynı ad üç düzeyde tanımlıysa en içteki kazanır
  ve öbürleri aramaya hiç girmez.
- Yerleşik düzeyin arama sırasında ayrıcalığı yoktur; en sonda olduğu için üstteki
  herhangi bir düzeyde aynı adın tanımlanması yerleşiği o gövdede erişilemez kılar.
- Bir adın yerel olmasına karar veren şey okumanın yeri değil, gövdede **bir yerde** ona
  atama yapılmasıdır; atama en sonda olsa bile ad gövdenin başından itibaren yereldir ve
  atamadan önceki okuma `UnboundLocalError` üretir.
- Gövdeden dışarı yazmak bildirimle olur: `nonlocal` atamayı kapsayan düzeye, `global`
  genel düzeye yönlendirir. Bildirimsiz atama dıştakini güncellemez, onu gölgeleyen yeni
  bir yerel ad kurar.
- Gövdedeki bir ad, işlev üretildiğinde değil **çağrıldığında** çözülür: döngüde üretilen
  üç işlev aynı adı paylaştığı için üçü de **`[2, 2, 2]`** döndürür; adı varsayılan değer
  olarak yakalamak sonucu **`[0, 1, 2]`** yapar.

#### Sonraki Adım

Arama zincirinin son basamağındaki adlar buraya kadar yalnız gölgelenecek bir şey olarak
göründü. Oysa o düzeyde duran adlar dilin en çok kullanılan işlevleridir ve her biri, ilk
iki dersin ölçtüğü protokollere doğrudan bağlanır: uzunluk soran, metne çeviren, yineleyici
isteyen, doğruluk sınayan, sıralayan. Sonraki ders bu yerleşikleri **sözleşmeleriyle**
ölçer — her birinin hangi özel yöntemi çağırdığını ve o yöntem tanımlı değilse ne olduğunu.
Ortaya çıkan şey, yerleşiklerin iş yapan işlevler değil, **protokol çağıran ince
kabuklar** olduğudur.

### Yerleşik Fonksiyonlar

Source: https://academia.sh/tr/kurslar/python-temelleri/yerlesik-fonksiyonlar

Yerleşikler iş yapan işlevler değil, protokol çağıran ince kabuklardır: sekiz yerleşik yirmi bir çağrı üretir, toplayanların hepsi aynı beşli diziyi kurar ve protokolü tanımlamayan bir nesnede üçü değer verirken beşi TypeError fırlatır.

Önceki ders ad aramasının dört düzeyini ölçtü ve son basamaktaki adları yalnız
gölgelenecek bir şey olarak gösterdi. O düzeyde duran adlar dilin en çok kullanılanlarıdır
ve şimdiye kadar ölçülen her protokole doğrudan bağlanırlar.

Bu dersin sorusu, o adların ne yaptığı değil — **kendilerinin bir iş yapıp yapmadığıdır**.
`len` uzunluğu kendisi mi hesaplıyor, yoksa nesneye mi soruyor? `sorted` sıralamayı kendisi
mi biliyor, yoksa karşılaştırmayı nesneye mi bırakıyor? Kursun ölçü ekseni bu soruya bir
yanıt öneriyor: sözdizim bir kısaltmaysa, yerleşikler de birer kısaltma olabilir. Ölçüm
bunu sınar.

#### Yerleşik Bir Kabuktur

Buraya kadarki dört ders sözdizim biçimlerini ölçtü: `if`, `for`, `while`, çağrı yazımı.
Yerleşikler sözdizim değildir; sıradan işlevlerdir ve çıplak bir adla çağrılırlar. Ama
ölçü ekseni ikisi için de aynıdır — bir yerleşiğin sayısı da, hangi özel yöntemi kaç kez
çağırdığıdır.

Sözleşme şudur: yerleşik, işi kendisi yapmaz. Nesnede adı belli bir özel yöntemi arar,
bulursa çağırır ve döndürdüğünü verir. `len` uzunluğu saymaz, `__len__`'e sorar; `str`
metni kurmaz, `__str__`'e sorar; `iter` bir yineleyici üretmez, `__iter__`'den ister.
Yerleşiğin katkısı iş değil, **tek bir çağrı adı**: nesnenin tipi ne olursa olsun aynı ad
kullanılır.

Bunun sonucu, yerleşiklerin tipe göre değil **protokole göre** çalışmasıdır. Bir nesnenin
`len` ile ölçülüp ölçülemeyeceğini belirleyen şey sınıfının adı değil, `__len__`
tanımlayıp tanımlamadığıdır.

Bu tasarımın pratik karşılığı, yeni bir sınıfın yerleşiklerle çalışmak için hiçbir şey
**devralmak** ya da hiçbir yere **kaydolmak** zorunda olmamasıdır. Sınıf yalnız beklenen
adı taşıyan bir yöntem tanımlar; yerleşik onu bulur. Ortak tanımın dördüncü okuması burada
ikinci kez ödenir — bir nesnenin ne yapabildiğini tipinin adı değil, tanımladığı özel
yöntemler belirler. Yerleşiğin nesne hakkında bildiği tek şey, aradığı yöntemin orada olup
olmadığıdır.

#### Toplayan Yerleşikler

İkinci bir öbek, tek bir yöntem çağırmakla yetinmez. `list`, `sum` ve `sorted` nesnenin
**bütün öğelerini** ister; bunun için yineleme protokolünü baştan sona yürütürler.
Beklenen dizi ikinci dersin ölçtüğü diziyle aynı olmalıdır: bir `__iter__`, ardından öğe
sayısından bir fazla `__next__`.

Bu öbeğin bir sonucu, öğeleri **toplamanın** her zaman öğe sayısı kadar iş olmasıdır:
yerleşik kaç öğe olduğunu baştan bilmez ve bilemez, çünkü yineleme protokolü bir sayı
bildirmez. Toplama ancak `StopIteration` geldiğinde biter — yani öğeler tükenene kadar
sürer.

`sorted` bu öbek içinde ayrı durur, çünkü topladıktan sonra bir de **sıralaması** gerekir.
Toplamak bir protokol, sıralamak başka bir protokoldür; ikincisi öğeler arasında
karşılaştırma yapar. Bir yerleşiğin tek bir özel yöntemle eşleştiği varsayımı burada
sınanır.

#### Protokol Yoksa

Üçüncü ölçü, yöntemin **bulunamadığı** durumdur. Ortak tanımın dördüncü okuması bunu
söylüyordu: protokol yoksa sözdizim de yoktur. Yerleşikler için de geçerli mi?

Yanıtın tek olmadığı baştan bellidir, çünkü birinci derste ölçülen bir durum var: doğruluk
sınaması, hiçbir yöntem bulamadığında hata vermiyor, nesneyi **doğru** sayıyordu. Demek ki
en az bir yerleşiğin varsayılan davranışı var. Kaçının var, kaçının yok — ölçüm bunu
ayırır.

Ölçümün varsayımları:

- **PA31** — Kâhin kurgunun kendisidir: her özel yöntem çağrıldığında adını kaydeder ve
  yerleşiğin hangi yöntemi çağırdığı bu kayıttan okunur.
- **PA32** — İki sınıf kullanılır. `Akis` yalnız yineleme ve karşılaştırma yöntemlerini
  tanımlar; `Izci` ona uzunluk ve metin yöntemlerini ekler. Toplayan yerleşikler
  **`Akis`** üzerinde ölçülür — böylece ölçülen dizi yalnız yineleme protokolünden gelir.
- **PA33** — `Sessiz` hiçbir özel yöntem tanımlamaz ve "protokol yok" durumunun ölçüsüdür.
- **PA34** — Sonuçlar **değer olarak basılmaz**, yalnız "değer üretti" ya da istisnanın
  sınıfı yazılır. Yöntem tanımlamayan bir nesnenin metin karşılığı ortama bağlı bilgi
  taşır; ölçüme girmez.
- **PA35** — `Izci.__repr__` gövdesinde `__str__`'i çağırır; bu, ortak tanımdaki
  davranıştır ve iki çağrının nedeni budur.
- **PA36** — Sıralama ölçümünde **çağrı sayısı değil, çağrılan protokollerin kümesi**
  yazılır. Karşılaştırma sayısı sıralama düzeninin bir ayrıntısıdır ve ölçünün parçası
  değildir.
- **PA37** — Toplam çağrı sayısı yalnız bu sekiz yerleşiğe ve üç öğelik nesneye özgüdür.
- **PA38** — Anahtar işlevi ölçümünde `key` olarak `len` yerleşiği verilir; böylece
  anahtarın çağrıldığı her yer kayda düşer ve öğe başına kaç kez çağrıldığı sayılabilir.

#### Ölçüm

```python
"""Yerlesiklerin sozlesmesi: her biri hangi ozel yontemi cagirir."""

KAYIT = []


def kaydet(ad):
    KAYIT.append(ad)


class Akis:
    """Yalniz yineleme ve siralama protokolu; __len__ tanimlamaz."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __iter__(self):
        kaydet("__iter__")
        self._i = 0
        return self

    def __next__(self):
        kaydet("__next__")
        if self._i >= len(self.ogeler):
            raise StopIteration
        deger = self.ogeler[self._i]
        self._i += 1
        return deger

    def __lt__(self, o):
        kaydet("__lt__")
        return len(self.ogeler) < len(o.ogeler)


class Izci(Akis):
    """Uzunluk ve metin protokollerini de tanimlar."""

    def __len__(self):
        kaydet("__len__")
        return len(self.ogeler)

    def __str__(self):
        kaydet("__str__")
        return f"Izci{tuple(self.ogeler)}"

    def __repr__(self):
        kaydet("__repr__")
        return self.__str__()


class Bosluksuz(Izci):
    """__bool__ tanimli: dogruluk sinamasi __len__'e dusmez."""

    def __bool__(self):
        kaydet("__bool__")
        return len(self.ogeler) > 0


class Sessiz:
    """Hicbir ozel yontem tanimlamaz."""


def olc(islev):
    KAYIT.clear()
    try:
        islev()
        durum = "değer üretti"
    except Exception as e:
        durum = f"!{type(e).__name__}"
    return list(KAYIT), durum


TEKIL = (("len(n)", len, Izci), ("str(n)", str, Izci), ("repr(n)", repr, Izci),
         ("bool(n)", bool, Izci), ("iter(n)", iter, Izci))
TOPLAYAN = (("list(n)", list, Akis), ("sum(n)", sum, Akis),
            ("sorted(n)", sorted, Akis))

print(f"{'yerleşik':<12s} {'çağrı':>6s}  çağrılan protokol dizisi")
toplam = 0
for ad, f, sinif in TEKIL + TOPLAYAN:
    c, _ = olc(lambda f=f, s=sinif: f(s()))
    toplam += len(c)
    print(f"  {ad:<10s} {len(c):6d}  {' '.join(c)}")
print(f"\nsekiz yerleşik, toplam {toplam} çağrı")

print()
print(f"{'yerleşik':<12s} {'protokolü tanımlayan nesne':<27s} tanımlamayan nesne")
for ad, f, sinif in TEKIL + TOPLAYAN:
    _, d1 = olc(lambda f=f, s=sinif: f(s()))
    _, d2 = olc(lambda f=f: f(Sessiz()))
    print(f"  {ad:<10s} {d1:<27s} {d2}")

print()
c, _ = olc(lambda: bool(Izci()))
print(f"bool(__bool__ tanımsız) -> {' '.join(c)}")
c, _ = olc(lambda: bool(Bosluksuz()))
print(f"bool(__bool__ tanımlı)  -> {' '.join(c)}")

print()
c, _ = olc(lambda: sorted(Akis()))
print(f"sorted(tek nesne, öğeleri sayı)  -> {' '.join(sorted(set(c)))}")
UCLU = [Akis((1, 2, 3)), Akis((1,)), Akis((1, 2))]
c, _ = olc(lambda: sorted(UCLU))
print(f"sorted(üç nesnelik liste)        -> {' '.join(sorted(set(c)))}")

print()
UCLU_IZCI = [Izci((1, 2, 3)), Izci((1,)), Izci((1, 2))]
print(f"{'üç nesnelik liste':<28s} {'çağrılan protokol':<20s} karşılaştırma nesneye sorulur mu")
for ad, f in (("sorted(liste)", lambda: sorted(UCLU_IZCI)),
              ("sorted(liste, key=len)", lambda: sorted(UCLU_IZCI, key=len))):
    c, _ = olc(f)
    print(f"  {ad:<26s} {' '.join(sorted(set(c))):<20s}"
          f" {'evet' if '__lt__' in c else 'hayır'}")
c, _ = olc(lambda: sorted(UCLU_IZCI, key=len))
print(f"  key=len çağrısı öğe başına: {c.count('__len__')} / {len(UCLU_IZCI)} öğe")
```

```
yerleşik      çağrı  çağrılan protokol dizisi
  len(n)          1  __len__
  str(n)          1  __str__
  repr(n)         2  __repr__ __str__
  bool(n)         1  __len__
  iter(n)         1  __iter__
  list(n)         5  __iter__ __next__ __next__ __next__ __next__
  sum(n)          5  __iter__ __next__ __next__ __next__ __next__
  sorted(n)       5  __iter__ __next__ __next__ __next__ __next__

sekiz yerleşik, toplam 21 çağrı

yerleşik     protokolü tanımlayan nesne  tanımlamayan nesne
  len(n)     değer üretti                !TypeError
  str(n)     değer üretti                değer üretti
  repr(n)    değer üretti                değer üretti
  bool(n)    değer üretti                değer üretti
  iter(n)    değer üretti                !TypeError
  list(n)    değer üretti                !TypeError
  sum(n)     değer üretti                !TypeError
  sorted(n)  değer üretti                !TypeError

bool(__bool__ tanımsız) -> __len__
bool(__bool__ tanımlı)  -> __bool__

sorted(tek nesne, öğeleri sayı)  -> __iter__ __next__
sorted(üç nesnelik liste)        -> __lt__

üç nesnelik liste            çağrılan protokol    karşılaştırma nesneye sorulur mu
  sorted(liste)              __lt__               evet
  sorted(liste, key=len)     __len__              hayır
  key=len çağrısı öğe başına: 3 / 3 öğe
```

#### Bire Bir Eşleşme

Üst tablonun ilk beş satırı sözleşmeyi doğruluyor. `len` **bir** çağrı yapıyor ve çağırdığı
şey `__len__`; `str` bir çağrı, `__str__`; `iter` bir çağrı, `__iter__`. Yerleşiğin
gövdesinde uzunluk sayan, metin kuran ya da yineleyici üreten bir kod yok — olsaydı çağrı
sayısı sıfır olurdu, çünkü nesneye hiç sormaya gerek kalmazdı.

`repr` satırındaki **2** bir istisna gibi görünüyor ama değil. `repr` yine tek bir yöntem
çağırıyor: `__repr__`. İkinci çağrı yerleşikten gelmiyor — `__repr__` gövdesinin kendisi
`__str__`'i çağırıyor. Yerleşiğin sözleşmesi bozulmuş değil; ölçülen dizi, çağrılan
yöntemin **kendi** yaptığı işi de gösteriyor.

`bool` satırı birinci dersin sonucunu tekrar üretiyor: nesnede `__bool__` tanımlı olmadığı
için sınama `__len__`'e düşüyor. Alt bölümdeki iki satır ikisini yan yana koyuyor —
`__bool__` tanımlıyken **yalnız o** çağrılıyor, `__len__` sınamaya hiç girmiyor. Bir
yerleşiğin tek bir yöntemle eşleştiği kuralının tek gerçek istisnası budur: `bool` bir
değil, **sıralı iki** yöntem arar.

#### Toplayanların Ortak Dizisi

Son üç satır aynı diziyi veriyor: bir `__iter__`, dört `__next__`, toplam **5**. Üç ayrı
yerleşik — biri liste kuruyor, biri toplam alıyor, biri sıralıyor — ve üçü de öğeleri tam
olarak aynı biçimde topluyor.

Dizi ikinci dersin ölçtüğü diziyle birebir aynıdır ve aynı nedenle **bir fazladır**: üç
öğe için dört `__next__`, sonuncusu `StopIteration` fırlatarak toplamanın bittiğini
bildiriyor. `for` döngüsü, `list`, `sum` ve `sorted` — dördü de aynı protokolü aynı sayıda
çağırıyor. Aralarındaki fark topladıkları öğelerle **ne yaptıklarıdır**, nasıl
topladıkları değil.

Bunun pratik sonucu, yineleme protokolünü tanımlayan bir nesnenin bu yerleşiklerin
tamamıyla kendiliğinden çalışmasıdır. Sınıfa `list` desteği, `sum` desteği, `sorted`
desteği ayrı ayrı eklenmez; `__iter__` ile `__next__` yazıldığı anda üçü de gelir.

#### Sıralamanın İki Protokolü

Alt iki satır `sorted` yerleşiğini ayırıyor ve tek yöntemle eşleşmediğini gösteriyor.

Birinci satırda sıralanan nesnenin **öğeleri sayı**. Çağrılan protokoller `__iter__` ve
`__next__` — yani yalnız toplama. Karşılaştırma da yapılıyor elbette, ama sayılar arasında;
kurgunun nesnesi o karşılaştırmaya girmiyor ve kayda hiçbir şey düşmüyor.

İkinci satırda sıralanan şey **üç nesnelik bir liste**. Bu kez kayıtta yalnız `__lt__` var.
Liste zaten bir liste olduğu için toplama aşamasında kurgunun protokolü çağrılmıyor;
sıralama aşamasında ise her karşılaştırma nesneye soruluyor.

İki satır birlikte okunduğunda `sorted` iki ayrı protokol çağırıyor: öğeleri **toplamak**
için yineleme protokolü, **sıralamak** için karşılaştırma protokolü. Hangisinin kayda
düştüğü, kurgunun nesnesinin hangi rolde bulunduğuna bağlı — dışta kapsayıcı olarak mı,
içte öğe olarak mı.

Buradan çıkan okuma şudur: `sorted` sıralamayı bilmez, **karşılaştırmayı** nesneye sorar.
Sıralama düzenini belirleyen şey yerleşiğin gövdesi değil, öğelerin `__lt__` gövdesidir.
Yerleşik yalnız hangi öğenin hangisinden önce geleceğini sorar ve gelen yanıta uyar.
Aynı liste, öğelerin `__lt__` tanımı değiştirilerek başka bir sırayla dizilir ve `sorted`
çağrısında tek bir karakter değişmez.

#### Anahtar Protokolü Değiştirir

Son tablo aynı çağrının bir anahtarlı argümanla nasıl **başka bir protokole** geçtiğini
ölçüyor. İki satırın sıraladığı liste aynı, sıralanan nesneler aynı; değişen tek şey
`key` argümanının verilip verilmediği.

Anahtarsız çağrıda kayda düşen protokol `__lt__` — karşılaştırma nesnelere soruluyor.
Anahtarlı çağrıda kayda düşen protokol `__len__` ve `__lt__` **hiç çağrılmıyor**. Sağdaki
sütun bunu doğrudan söylüyor: karşılaştırma artık nesneye sorulmuyor.

Olan şey şudur: `key` verildiğinde `sorted` önce her öğeye anahtar işlevini uygular, sonra
sıralamayı **dönen anahtar değerleri** üzerinde yapar. Nesneler kendi aralarında hiç
karşılaştırılmaz; karşılaştırılan şey onların anahtarlarıdır. Bu yüzden `__lt__`
tanımlamayan nesneler bile uygun bir anahtarla sıralanabilir — sıralanabilirlik nesnenin
değil, **anahtarın** özelliği hâline gelir.

Son satır anahtarın kaç kez çağrıldığını veriyor: üç öğe için **3** çağrı, yani öğe başına
tam bir kez. Anahtar her karşılaştırmada yeniden hesaplanmıyor; bir kez hesaplanıp
saklanıyor. Bunun sonucu, pahalı bir anahtar işlevinin sıralama boyunca bir kez ödenmesidir
— karşılaştırma sayısı ne olursa olsun.

#### Varsayılanı Olan ve Olmayan

Orta tablo protokolün bulunamadığı durumu ölçüyor ve sekiz yerleşik **ikiye ayrılıyor**.

Beşi hiçbir varsayılan tanımıyor: `len`, `iter`, `list`, `sum` ve `sorted`, yöntemi
bulamadığında bir değer değil `TypeError` üretiyor. Bu yerleşikler için ortak tanımın
dördüncü okuması harfiyen geçerli — protokol yoksa yerleşik de yoktur.

Üçü değer üretiyor. `str` ile `repr`, yöntem tanımlanmamış bir nesne için de bir metin
veriyor; dil her nesne için varsayılan bir metin karşılığı tanımlar. `bool` ise birinci
dersin üçüncü basamağını uyguluyor ve nesneyi doğru sayıyor.

Ayrımın gerekçesi sorunun yanıtlanabilir olup olmamasıdır. "Bu nesnenin uzunluğu kaçtır?"
sorusunun nesne söylemezse makul bir yanıtı yoktur; uydurulan her sayı yanlış olurdu.
"Bu nesnenin metin karşılığı nedir?" sorusunun ise her nesne için üretilebilecek bir
yanıtı vardır. Yerleşiğin varsayılanı olması, sorunun **nesneden bağımsız** bir yanıtının
bulunmasına bağlıdır.

Bu ayrımın bir bedeli var ve tabloda görünmüyor: varsayılanı olan yerleşikler **sessizce**
çalışır. `__len__` yazmayı unutan bir sınıf `len` çağrısında hemen belli olur; `__bool__`
yazmayı unutan sınıf ise hiçbir belirti vermeden her `if` sınamasından doğru döner.
Hatanın erken çıkması, `TypeError` üreten yerleşiklerin sağladığı bir güvencedir.

#### Özet

- Yerleşikler iş yapan işlevler değil, nesnedeki özel yöntemi çağıran **ince kabuklardır**;
  `len` uzunluğu saymaz, `str` metni kurmaz, `iter` yineleyici üretmez — üçü de sorar.
- Sekiz yerleşik üç öğelik nesnede toplam **21** çağrı üretir; `len`, `str`, `repr` ve
  `iter` tek bir yöntemle eşleşir — `repr` satırındaki ikinci çağrı yerleşikten değil
  `__repr__` gövdesinden gelir —, `bool` ise sıralı iki yöntem arar ve `__bool__` yoksa
  `__len__`'e düşer.
- `list`, `sum` ve `sorted` aynı beşli diziyi kurar — bir `__iter__`, dört `__next__` — ve
  bu, `for` döngüsünün dizisiyle birebir aynıdır; yineleme protokolünü yazan bir sınıf
  üçünü birden kazanır.
- `sorted` tek değil **iki** protokol çağırır: toplamak için yineleme, sıralamak için
  karşılaştırma. Sıralama düzenini yerleşiğin gövdesi değil, öğelerin `__lt__` gövdesi
  belirler.
- Anahtarlı argüman verildiğinde `sorted` karşılaştırmayı nesneye hiç sormaz: `__lt__`
  yerine anahtar işlevi çağrılır — öğe başına **tam bir kez** — ve sıralanabilirlik
  nesnenin değil anahtarın özelliği olur.
- Protokolü tanımlamayan bir nesnede sekiz yerleşiğin **beşi** `TypeError` verir, **üçü**
  değer üretir; varsayılanı olanlar nesneden bağımsız bir yanıtı bulunanlardır ve
  eksikliği sessizce geçiştirme bedelini taşırlar.

#### Sonraki Adım

Buraya kadar ölçülen her protokol **beklenen** yolu izledi. Sözdizim bir yöntem aradı,
buldu ve çağırdı; bulamadığında ikinci bir yönteme düştü; o da yoksa ya bir varsayılan
uyguladı ya da düzenli bir biçimde `TypeError` fırlattı. Döngüyü bitiren `StopIteration`
bile bir kaza değil, protokolün planlanmış son adımıydı.

Peki bir protokol **başarısız olursa**? Çağrılan yöntem bulunuyor, çalışıyor, ama işini
tamamlayamıyor — dosya yok, sayı çevrilemiyor, dizin sınırın dışında. Bu derste
`TypeError` bir kez göründü, önceki derslerde `NameError`, `UnboundLocalError`, `IndexError`
ve `StopIteration` göründü. Beşi de birer sınıftı ve hepsi aynı yazımla yakalanabilir.
Sonraki ders bu sınıfların birbiriyle ilişkisini kurar: hangisini yakalamak hangilerini de
yakalar ve geniş bir yakalama neyi yutar.

## Hata Yönetimi ve Dosyalar

İstisna hiyerarşisi ve yeniden fırlatma, alan hatalarını tiple ifade etme, metin ile ikili kipin kodlama sonuçları, bağlam yöneticisi protokolü ve düzenli ifadelerle örüntü eşleme.

### İstisnalar

Source: https://academia.sh/tr/kurslar/python-temelleri/istisnalar

İ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.

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

```python
"""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.

```python
"""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 **1**–**2** 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.

### Özel İstisnalar

Source: https://academia.sh/tr/kurslar/python-temelleri/ozel-istisnalar

Ö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.

Ö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:

```text
# 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**.

```python
"""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.

```python
"""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.

### Dosya İşlemleri

Source: https://academia.sh/tr/kurslar/python-temelleri/dosya-islemleri

Aynı 41 bayt ikili kipte 41 uzunluğunda bir bayt dizisi, metin kipinde utf-8 ile 34 karakterlik bir dizgi verir; yanlış kodlama açma değil okuma anında kırılır ve latin-1 hiç kırılmayıp sessizce 41 yanlış karakter üretir.

Önceki dersin on iki kaydı bellekte hazır duruyordu. `deger` alanı zaten bir dizgiydi; tek iş
onu sayıya çevirmek ve alanın sınırlarına vurmaktı. Alan kökü işe yaradı çünkü hatanın
kaynağı alanın kendisiydi.

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. Dosya, kodlama ve bayt
kavramları Bilgisayarlar Nasıl Çalışır kursunda kuruldu; orada **kavram** kuruldu, burada
Python'un iki dosya kipinin aynı baytlarda ne verdiği ölçülüyor.

#### İki Kip, İki Tür

`open` iki kipte çalışır ve aralarındaki fark bir seçenek değil, **dönen nesnenin türüdür**.

**İkili kip** (`"rb"`, `"wb"`) bayt dizisi verir. Dosyada ne varsa odur; hiçbir çeviri yapılmaz.
Uzunluk **bayt sayısıdır**.

**Metin kipi** (`"r"`, `"w"`) dizgi verir. Araya bir çözücü girer: baytlar bir **kodlamaya**
göre karakterlere çevrilir ve satır sonları çevrilir. Uzunluk **karakter sayısıdır**.

Metin kipinde `encoding` verilmezse yorumlayıcı bir öntanımlı seçer ve o öntanımlı **çalıştığı
ortama bağlıdır**. Aynı dosya, aynı program, başka bir ortam — başka sonuç. Bu dersin bütün
ölçümleri kodlamayı açıkça yazar; ölçülebilir olmasının koşulu budur.

#### Aynı Baytlar, Ayrı Kodlamalar

Ölçüm tek bir metinle başlar: Türkçe harfler, bir derece işareti ve bir uzun çizgi içeren bir
ölçüm satırı. Bu metin utf-8 ile kodlanır ve elde edilen baytlar **beş ayrı kodlamayla**
çözülmeye çalışılır.

Ölçümün varsayımları:

- **HD19** — Bütün ölçüm tek bir bayt dizisi üzerinde yapılır; dizi metinden **kodlanarak**
  üretilir, elle yazılmaz.
- **HD20** — Kâhin kaynak metnin kendisidir: çözülen dizgi kaynakla **özdeş** mi diye
  sorulabilir, çünkü kaynağı biz ürettik.
- **HD21** — "Kırılan bayt konumu" istisna nesnesinin kendi alanından okunur; hata iletisi
  ayrıştırılmaz.
- **HD22** — Kodlama adları dilin kendi çözücü kataloğundan gelir; hiçbiri dış bir araca ait
  değildir.
- **HD23** — Parça boyu taraması aynı baytları farklı boylarda böler ve her parçayı **ayrı
  ayrı** çözer; parçalar arasında durum taşınmaz.

```python
"""Ayni baytlar, ayri kodlamalar: cozulur mu, ozdes mi, nerede kirilir."""

METIN = "kuzey yamaç: 12,5 °C — ölçüm tamam"
HAM = METIN.encode("utf-8")

print(f"karakter {len(METIN)} | utf-8 bayt {len(HAM)} | "
      f"çok baytlı karakter {sum(len(k.encode('utf-8')) > 1 for k in METIN)}")

KODLAMALAR = ("utf-8", "utf-16", "latin-1", "ascii", "cp1254")

print()
print(f"{'kodlama':<12s}{'çözüldü':<10s}{'karakter':>9s}{'özdeş':>8s}"
      f"   kırılan sınıf / bayt konumu")
for ad in KODLAMALAR:
    try:
        cozulen = HAM.decode(ad)
    except UnicodeDecodeError as e:
        print(f"  {ad:<10s}{'hayır':<10s}{'-':>9s}{'-':>8s}"
              f"   {type(e).__name__} / {e.start}")
    else:
        print(f"  {ad:<10s}{'evet':<10s}{len(cozulen):>9d}"
              f"{('evet' if cozulen == METIN else 'hayır'):>8s}   -")

print()
print(f"{'hata kipi':<20s}{'karakter':>9s}{'özdeş':>8s}   ilk yedi karakter")
for kip in ("ignore", "replace", "backslashreplace"):
    c = HAM.decode("ascii", errors=kip)
    print(f"  {kip:<18s}{len(c):>9d}{('evet' if c == METIN else 'hayır'):>8s}"
          f"   {c[:7]!r}")

print()
print(f"{'parça boyu':<12s}{'parça':>7s}{'kırılan parça':>15s}")
for boy in range(4, 25, 2):
    parcalar = [HAM[i:i + boy] for i in range(0, len(HAM), boy)]
    kirilan = 0
    for p in parcalar:
        try:
            p.decode("utf-8")
        except UnicodeDecodeError:
            kirilan += 1
    print(f"  {boy:<10d}{len(parcalar):>7d}{kirilan:>15d}")
butun = 0
try:
    HAM.decode("utf-8")
except UnicodeDecodeError:
    butun = 1
print(f"aynı baytlar bütün olarak çözüldüğünde kırılan {butun}")
```

```
karakter 34 | utf-8 bayt 41 | çok baytlı karakter 6

kodlama     çözüldü    karakter   özdeş   kırılan sınıf / bayt konumu
  utf-8     evet             34    evet   -
  utf-16    hayır             -       -   UnicodeDecodeError / 40
  latin-1   evet             41   hayır   -
  ascii     hayır             -       -   UnicodeDecodeError / 10
  cp1254    evet             41   hayır   -

hata kipi            karakter   özdeş   ilk yedi karakter
  ignore                   28   hayır   'kuzey y'
  replace                  41   hayır   'kuzey y'
  backslashreplace         80   hayır   'kuzey y'

parça boyu    parça  kırılan parça
  4              11              4
  6               7              2
  8               6              2
  10              5              2
  12              4              2
  14              3              2
  16              3              0
  18              3              0
  20              3              2
  22              2              0
  24              2              2
aynı baytlar bütün olarak çözüldüğünde kırılan 0
```

#### Sessiz Yanlış

İlk satır dersin çekirdeğini veriyor: **34** karakterlik metin **41** bayt tutuyor. Aradaki
**7** baytlık fark, çok baytlı **6** karakterden geliyor. Karakter sayısı ile bayt sayısı
birbirinden bağımsızdır ve hangisini saydığınız hangi kipte okuduğunuza bağlıdır.

Kodlama tablosu üç ayrı davranış gösteriyor. **utf-8** çözüyor, **34** karakter veriyor ve
kaynakla **özdeş**. Doğru kodlamanın tanımı budur.

**ascii** ve **utf-16** çözemiyor ve `UnicodeDecodeError` fırlatıyor. Konumlar ayrı:
ascii **10**'uncu baytta kırılıyor — ilk Türkçe harfin ilk baytında. utf-16 ise **40**'ıncı
baytta kırılıyor, yani neredeyse sonda; çünkü utf-16 baytları ikişerli okur ve **41** tek
sayıdır, son bayt eşsiz kalır. İkisi de kırılır ama **nerede** kırıldıkları kodlamanın
kendi kuralından gelir.

Asıl bulgu **latin-1** ve **cp1254** satırlarıdır. İkisi de **çözüyor** — hiç istisna
fırlatmıyor — ve **41** karakter veriyor. Yani her baytı bir karaktere çeviriyorlar. Ama
**özdeş değil**. Tek baytlı bir kodlama, önüne gelen her baytı geçerli sayar; kırılacak bir
şey yoktur. Bu, ölçümün en tehlikeli sonucudur: **yanlış kodlama her zaman istisna
üretmez.** İstisna fırlatan kodlama sizi uyarır; fırlatmayan kodlama sessizce yanlış metin
verir ve yanlışlık ancak metni okuyan insan tarafından fark edilir.

Hata kipleri bunun bilinçli hâlidir. `ignore` çözemediği baytları atıyor ve **28** karakter
kalıyor — **6** karakterin baytları düşmüş. `replace` her kusurlu baytın yerine bir karakter
koyuyor ve **41** çıkıyor; sayı bayt sayısına eşitleniyor çünkü değiştirme bayt başına
yapılıyor. `backslashreplace` her kusurlu baytı dört karakterlik bir yazımla anlatıyor ve
uzunluk **80**'e çıkıyor. Üçünün de `özdeş` sütunu **hayır**: hata kipi vermek, veriyi
kurtarmaz, yalnız **kırılmayı** engeller.

#### Parçanın Sınırı

Son tablo ikili kipte parça parça okumanın bedelini veriyor. Aynı **41** bayt, farklı
boylarda bölünüp her parça **ayrı ayrı** çözülüyor.

Sonuç düzensiz. **4** baytlık parçalarda **11** parçanın **4**'ü kırılıyor; **16** ve **18**
boyunda hiçbiri kırılmıyor; **20**'de yine **2** parça kırılıyor, **22**'de **0**, **24**'te
**2**. Kırılma sayısı parça boyunun büyüklüğüne bağlı değil, parça sınırının **çok baytlı bir
karakterin ortasına düşüp düşmediğine** bağlı. Aynı baytlar **bütün olarak** çözüldüğünde
kırılan **0**.

Bunun doğrudan sonucu şudur: ikili kipte okuyup baytları kendiniz çözüyorsanız, parçalar
arasında **durum taşımak** sizin işinizdir. Metin kipi bu işi zaten yapar — çözücü parça
sınırında yarım kalan karakteri bekletir. İkili kip hızlı ve tarafsızdır; çeviriyi
üstlenmediği için çevirinin sorunlarını da üstlenmez ve onları çağırana bırakır.

#### Kip, Tür ve Kırılma Noktası

İkinci ölçüm gerçek bir dosyayla çalışır. Dosya **geçici bir dizinde** kurulur ve ölçüm
bitince dizin silinir; ölçüm hiçbir kalıcı iz bırakmaz.

- **HD24** — Dosya geçici bir dizinde üretilir ve baytları önceki ölçümün metninden gelir;
  hiçbir yol adı basılmaz, çünkü yol ortama bağlıdır.
- **HD25** — "Uzunluk" ikili kipte bayt, metin kipinde karakter sayısıdır; ikisi aynı adla
  ölçülür ama aynı şey değildir.
- **HD26** — Kırılma noktası ölçümü açma ile okumayı **ayrı adım** olarak dener; hangisinin
  istisna ürettiği koşumdan okunur.
- **HD27** — Satır sonu ölçümünde dosya, biri iki karakterlik biri tek karakterlik olmak
  üzere **iki ayrı satır sonu biçimi** taşır; çeviri farkı bundan doğar.

```python
"""Metin kipi ile ikili kip: ayni dosya, ayri tur, ayri uzunluk, ayri kirilma noktasi."""
import tempfile
from pathlib import Path

METIN = "kuzey yamaç: 12,5 °C — ölçüm tamam"
SATIRLI = b"kuzey\r\nyama\xc3\xa7\nolcum\r\n"

with tempfile.TemporaryDirectory() as gecici:
    olcum = Path(gecici) / "olcum.dat"
    olcum.write_bytes(METIN.encode("utf-8"))
    satir = Path(gecici) / "satir.dat"
    satir.write_bytes(SATIRLI)

    print(f"{'okuma kipi':<22s}{'tür':<8s}{'uzunluk':>8s}{'özdeş':>8s}")
    with open(olcum, "rb") as d:
        ikili = d.read()
    print(f"  {'ikili':<20s}{type(ikili).__name__:<8s}{len(ikili):>8d}{'-':>8s}")
    for ad, kodlama, kip in (("metin utf-8", "utf-8", "strict"),
                             ("metin latin-1", "latin-1", "strict"),
                             ("metin ascii replace", "ascii", "replace")):
        with open(olcum, "r", encoding=kodlama, errors=kip) as d:
            metin = d.read()
        print(f"  {ad:<20s}{type(metin).__name__:<8s}{len(metin):>8d}"
              f"{('evet' if metin == METIN else 'hayır'):>8s}")

    print()
    print("adım                sonuç")
    try:
        d = open(olcum, "r", encoding="ascii")
    except UnicodeDecodeError as e:
        print(f"  {'open':<18s}{type(e).__name__} / bayt {e.start}")
    else:
        print(f"  {'open':<18s}başarılı, dosya açıldı")
        try:
            d.read()
        except UnicodeDecodeError as e:
            print(f"  {'read':<18s}{type(e).__name__} / bayt {e.start}")
        finally:
            d.close()

    print()
    print(f"{'okuma biçimi':<22s}{'uzunluk':>8s}{'CR sayısı':>11s}{'satır':>7s}")
    with open(satir, "rb") as d:
        ham = d.read()
    print(f"  {'ikili':<20s}{len(ham):>8d}{ham.count(13):>11d}"
          f"{len(ham.splitlines()):>7d}")
    for ad, yenisatir in (("metin (öntanımlı)", None), ('metin newline=""', "")):
        with open(satir, "r", encoding="utf-8", newline=yenisatir) as d:
            m = d.read()
        with open(satir, "r", encoding="utf-8", newline=yenisatir) as d:
            satirlar = d.readlines()
        print(f"  {ad:<20s}{len(m):>8d}{m.count(chr(13)):>11d}"
              f"{len(satirlar):>7d}")

print("geçici dizin silindi:", not Path(gecici).exists())
```

```
okuma kipi            tür      uzunluk   özdeş
  ikili               bytes         41       -
  metin utf-8         str           34    evet
  metin latin-1       str           41   hayır
  metin ascii replace str           41   hayır

adım                sonuç
  open              başarılı, dosya açıldı
  read              UnicodeDecodeError / bayt 10

okuma biçimi           uzunluk  CR sayısı  satır
  ikili                     21          2      3
  metin (öntanımlı)         18          0      3
  metin newline=""          20          2      3
geçici dizin silindi: True
```

#### Sayıların Okunması

Birinci tablo iki kipin farkını türle veriyor. İkili kip `bytes` döndürüyor ve uzunluk
**41**; metin kipi `str` döndürüyor ve doğru kodlamayla uzunluk **34**. Aynı dosya, aynı
içerik, iki ayrı sayı — ve ikisi de doğru. `latin-1` yine **41** veriyor ve `özdeş` sütunu
`hayır`: bellekteki ölçümde görülen sessiz yanlış, dosyadan okurken de aynen tekrarlanıyor.

İkinci tablo dersin en kesin bulgusudur. `open` çağrısı `ascii` kodlamasıyla **başarılı
oluyor** — dosya açılıyor, hiçbir istisna çıkmıyor. İstisna `read` çağrısında, **10**'uncu
baytta çıkıyor. `open` yalnız dosyayı hazırlar ve kodlama adını saklar; çözme işi ilk okumada
yapılır. Kodlama hatası bir **açma hatası değil, okuma hatasıdır** ve bunun pratik sonucu
şudur: `open` çağrısını saran bir `try` bloğu bu hatayı yakalayamaz. Yakalayacak blok okumayı
sarmalıdır.

Üçüncü tablo satır sonu çevirisini sayıyor. Dosya **21** bayt ve içinde **2** tane satır başı
karakteri var, çünkü iki satır iki karakterlik satır sonuyla, biri tek karakterlik satır
sonuyla bitiyor. Metin kipi öntanımlı ayarıyla okuduğunda uzunluk **18**'e iniyor ve satır
başı sayısı **0** oluyor: çözücü her satır sonu biçimini tek bir karaktere çevirdi.
`newline=""` verildiğinde çeviri kapanıyor ve uzunluk **20**, satır başı sayısı **2** oluyor.

Satır sayısı üç okumada da **3**. Çeviri satırların **nerede bittiğini** değiştirmez, yalnız
satır sonunun kaç karakterle yazıldığını değiştirir. Metin kipinde okunan bir dosyanın
uzunluğunu dosyanın boyutuyla karşılaştırmak bu yüzden yanlış sonuç verir: hem kodlama hem
satır sonu çevirisi araya girer.

#### Yazma Tarafı ve Açma Kipleri

Okuma tarafı kodlamayı **varsayar**; yazma tarafı onu **kurar**. Üçüncü ölçüm aynı metni beş
kodlamayla yazmayı dener ve her birinde gidiş–dönüş sınaması yapar: kodlanan baytlar aynı
kodlamayla geri çözüldüğünde kaynak metin geri gelir mi?

- **HD28** — Gidiş–dönüş sınaması her kodlamada **kendi** kodlamasıyla yapılır; ölçülen şey
  kodlamanın metni taşıyıp taşıyamadığıdır.
- **HD29** — Açma kipleri aynı dosya üzerinde **sırayla** denenir; her adımda dosyanın bayt
  sayısı dosya sisteminden okunur, hesaplanmaz.
- **HD30** — Dosya yine geçici bir dizinde kurulur; hiçbir yol basılmaz ve ölçüm bitince dizin
  silinir.

```python
"""Yazma tarafi: kodlanamayan karakter ve dosya acma kipleri."""
import tempfile
from pathlib import Path

METIN = "kuzey yamaç: 12,5 °C — ölçüm tamam"

print(f"{'kodlama':<12s}{'kodlandı':<10s}{'bayt':>6s}"
      f"{'gidiş-dönüş özdeş':>19s}   kırılan sınıf / konum")
for ad in ("utf-8", "utf-16", "latin-1", "ascii", "cp1254"):
    try:
        ham = METIN.encode(ad)
    except UnicodeEncodeError as e:
        print(f"  {ad:<10s}{'hayır':<10s}{'-':>6s}{'-':>19s}"
              f"   {type(e).__name__} / {e.start}")
    else:
        ozdes = "evet" if ham.decode(ad) == METIN else "hayır"
        print(f"  {ad:<10s}{'evet':<10s}{len(ham):>6d}{ozdes:>19s}   -")

print()
with tempfile.TemporaryDirectory() as gecici:
    gunluk = Path(gecici) / "olcum.log"
    print(f"{'kip':<6s}{'çağrı':<8s}{'dosya baytı':>12s}   sonuç")
    for sira, kip in (("1.", "w"), ("2.", "w"), ("3.", "a"), ("4.", "x")):
        try:
            with open(gunluk, kip, encoding="utf-8") as d:
                d.write(METIN + "\n")
        except FileExistsError as e:
            print(f"  {kip:<4s}{sira:<8s}{gunluk.stat().st_size:>12d}"
                  f"   {type(e).__name__}")
        else:
            print(f"  {kip:<4s}{sira:<8s}{gunluk.stat().st_size:>12d}   yazıldı")
    with open(gunluk, "r", encoding="utf-8") as d:
        satirlar = d.readlines()
    print(f"son durumda satır {len(satirlar)}, "
          f"karakter {sum(len(s) for s in satirlar)}, "
          f"bayt {gunluk.stat().st_size}")
```

```
kodlama     kodlandı    bayt  gidiş-dönüş özdeş   kırılan sınıf / konum
  utf-8     evet          41               evet   -
  utf-16    evet          70               evet   -
  latin-1   hayır          -                  -   UnicodeEncodeError / 21
  ascii     hayır          -                  -   UnicodeEncodeError / 10
  cp1254    evet          34               evet   -

kip   çağrı    dosya baytı   sonuç
  w   1.                42   yazıldı
  w   2.                42   yazıldı
  a   3.                84   yazıldı
  x   4.                84   FileExistsError
son durumda satır 2, karakter 70, bayt 84
```

`cp1254` satırı okuma tablosunun karşıtıdır ve dersin en öğretici çiftidir. Orada `cp1254` utf-8
baytlarını **sessizce yanlış** çözüyordu; burada aynı kodlama metni **34** bayta yazıyor ve
gidiş–dönüş **özdeş** çıkıyor. Kodlama bir dosyanın özelliği değildir; **yazanla okuyan
arasındaki anlaşmadır** ve dosyanın kendisi o anlaşmayı taşımaz. Yanlış olan `cp1254` değildi,
utf-8 ile yazılmış baytları `cp1254` sanmaktı.

Yazma tarafı okuma tarafından bir noktada ayrılır: `latin-1` ve `ascii` burada **kırılıyor**.
`latin-1` **21**'inci karakterde, uzun çizgide; `ascii` **10**'uncu karakterde, ilk Türkçe
harfte. Yazarken kodlanamayan karakter sessizce geçemez, çünkü karakterin karşılığı
kodlamada **yoktur**. Okurken sessiz yanlış mümkündü, çünkü her baytın bir karşılığı vardı.
`utf-16` metni **70** bayta yazıyor — utf-8'in **41**'ine karşı — ve o da özdeş dönüyor;
kodlama seçimi doğruluk kadar **boyut** kararıdır.

Açma kipleri tablosu üç ayrı davranışı ayırıyor. `"w"` ilk çağrıda **42** bayt yazıyor; ikinci
`"w"` çağrısından sonra dosya yine **42** — kip dosyayı **budayıp** baştan yazdı, ikinci kayıt
birinciyi sildi. `"a"` çağrısı **84**'e çıkarıyor: ekleme kipi budamaz. `"x"` çağrısı hiç
yazmadan `FileExistsError` fırlatıyor ve bayt sayısı **84**'te kalıyor. Son satır dosyanın
**2** satır, **70** karakter ve **84** bayt tuttuğunu söylüyor; karakter ile bayt farkı burada
da duruyor.

#### Özet

- İkili kip `bytes` verir ve uzunluk bayt sayısıdır; metin kipi `str` verir, uzunluk karakter
  sayısıdır — aynı dosya **41** ve **34** okunur.
- Yanlış kodlama her zaman kırılmaz: `ascii` **10**'uncu baytta, `utf-16` **40**'ıncı baytta
  kırılırken `latin-1` ve `cp1254` hiç kırılmaz ve sessizce **41** yanlış karakter üretir.
- Hata kipleri kırılmayı engeller, veriyi kurtarmaz: `ignore` **28**, `replace` **41**,
  `backslashreplace` **80** karakter verir ve hiçbiri kaynakla özdeş değildir.
- Baytları parça parça çözmek parça sınırına bağlıdır: aynı **41** bayt boya göre **0**, **2**
  ya da **4** parçada kırılır, bütün olarak çözüldüğünde **0**.
- Kodlama hatası `open` çağrısında değil `read` çağrısında çıkar; satır sonu çevirisi uzunluğu
  **21**'den **18**'e indirir ama satır sayısını **3** olarak korur.
- Kodlama dosyanın değil, yazanla okuyanın özelliğidir: `cp1254` yazarken **34** baytla özdeş
  gidiş–dönüş verirken utf-8 baytlarını okurken sessizce bozar; yazarken kodlanamayan karakter
  `latin-1`'de **21**'inci, `ascii`'de **10**'uncu karakterde kırılır.

#### Sonraki Adım

Bu dersin bütün dosya örnekleri `with` bloğu içinde açıldı ve blok bitince dosya kapandı —
ölçüm sonunda geçici dizin bile silinebildi, çünkü açık kalan bir tutamak kalmamıştı. `with`
şimdiye kadar bir alışkanlık olarak kullanıldı; oysa o da bir sözdizimdir ve altında bir
protokol vardır. Sonraki ders o protokolü ölçer: `with` girerken hangi özel yöntemi çağırır,
çıkarken hangisini ve blok içinde **bir istisna oluştuğunda** ne değişir. Ortak tanımın
`with n + istisna` satırının üç çağrısı orada ödenir.

### Bağlam Yöneticileri

Source: https://academia.sh/tr/kurslar/python-temelleri/baglam-yoneticileri

with bloğu girerken __enter__, çıkarken __exit__ çağırır; istisnasız blokta 2 çağrı olur, istisnalı blokta 3 ve __exit__ istisnayı görür — dönüş değeri True olduğunda çağrı yine 2'ye iner çünkü istisna dışarı çıkmaz.

Önceki dersin bütün dosya örnekleri `with` bloğu içinde açıldı ve blok bitince dosya kapandı.
Ölçüm sonunda geçici dizin bile silinebildi, çünkü açık kalan bir tutamak kalmamıştı. `with`
şimdiye kadar bir alışkanlık olarak kullanıldı.

Oysa `with` de bir sözdizimdir ve bu kursun kuralı şudur: her sözdizim bir **kısaltmadır** ve
adı belli bir **özel yöntemi** çağırır. Bu ders o protokolü ölçer. Kaynak yaşam süresi ve
kaynağın serbest bırakılması İşletim Sistemi Kavramları kursunda kuruldu; orada **kavram**
kuruldu, burada Python'un onu hangi protokolle gerçekleştirdiği ölçülüyor: `with` girerken
`__enter__`, çıkarken `__exit__` çağırır — ve blok içinde **bir istisna oluştuğunda**
`__exit__` **yine çağrılır**.

#### Protokol İki Yöntemden İbarettir

Bir nesnenin `with` deyiminde kullanılabilmesi için tek koşul vardır: `__enter__` ve
`__exit__` tanımlamak. Tipinin adı, hangi sınıftan indiği, ne iş yaptığı önemsizdir. Protokol
yoksa sözdizim de yoktur; protokol varsa nesne `with` içine girer.

`__enter__` argümansızdır ve döndürdüğü değer `as` ile bağlanan addır. Kaynağın kendisini
döndürmek zorunda değildir; başka bir nesne de döndürebilir.

`__exit__` **üç argüman** alır: oluşan istisnanın **türü**, **nesnesi** ve **izi**. Blok
istisnasız bittiyse üçü de boştur. Ve `__exit__` bir değer **döndürür**; bu dönüş değeri
istisnanın yutulup yutulmayacağına karar verir. Doğru sayılan bir dönüş istisnayı yutar,
yanlış sayılan bir dönüş onu dışarı bırakır.

Ortak kurgunun `Izci` nesnesi bu iki yöntemi tanımlar ve `__exit__` yöntemi `False` döndürür —
yani **yutmaz**. Ölçüm buna bir kardeş ekler: `Yutan`, yalnız dönüş değerini değiştirir.

#### Ölçümün Kurgusu

Üç sözdizim biçimi çalıştırılır: istisnasız `with`, istisnalı `with` ve istisnalı `with` ama
yutan bir nesneyle. Her biçimde hangi özel yöntemin kaç kez çağrıldığı sayılır. Ölçüm kaydı
istisnayı da bir olay olarak yazar: bir biçim istisnayla bittiyse kayda `!` önekiyle sınıf
adı düşer.

- **HD31** — `Izci`'nin `__enter__` ve `__exit__` yöntemleri ortak kurgudan değiştirilmeden
  alınır; `__exit__` yine `False` döndürür.
- **HD32** — `Yutan` yalnız `__exit__` dönüşünü değiştirir ve üst sınıfın kaydını **bozmadan**
  çağırır; kayıt düzeni ortak kurgunun düzenidir.
- **HD33** — `Tanik` de üst sınıfın kaydını bozmaz; ek olarak `__exit__`'in gördüğü üçlüyü
  ayrı bir listeye yazar. `Izci`'nin davranışına dokunulmaz.
- **HD34** — Çağrı sayısı kayıttaki öğe sayısıdır; `!ValueError` da bir öğedir, çünkü ölçülen
  şey **biçimin ürettiği olay dizisidir**, yalnız yöntem çağrıları değil.
- **HD35** — Dönüş değeri sınaması `__exit__` doğrudan çağrılarak okunur; yutup yutmadığı ise
  gerçek bir `with` bloğuyla ayrıca sınanır. İki yol birbirini doğrular.

```python
"""Baglam yoneticisi protokolu: with girerken ve cikarken hangi yontem cagrilir."""

KAYIT = []
GORULEN = []


def kaydet(ad):
    KAYIT.append(ad)


class Izci:
    """Ortak tanimin cekirdegi; burada baglam parcasi kullaniliyor."""

    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __enter__(self):
        kaydet("__enter__")
        return self

    def __exit__(self, tur, deger, iz):
        kaydet("__exit__")
        return False          # istisnayi yutmaz


class Yutan(Izci):
    """__exit__ True dondurur: istisna disari cikmaz."""

    def __exit__(self, tur, deger, iz):
        super().__exit__(tur, deger, iz)
        return True


class Tanik(Izci):
    """__exit__'in gordugu ucluyu ayrica kaydeder; Izci'nin kaydini bozmaz."""

    def __init__(self, ad, yutar=False, ogeler=(1, 2, 3)):
        super().__init__(ogeler)
        self.ad = ad
        self.yutar = yutar

    def __exit__(self, tur, deger, iz):
        GORULEN.append((self.ad, tur.__name__ if tur else "-",
                        str(deger) if deger else "-", iz is not None))
        super().__exit__(tur, deger, iz)
        return self.yutar


def olc(islev):
    """Bir sozdizim bicimini calistirir ve cagrilan protokolleri dondurur."""
    KAYIT.clear()
    try:
        islev()
    except Exception as e:
        KAYIT.append(f"!{type(e).__name__}")
    return list(KAYIT)


def baglam():
    with Izci():
        pass


def baglam_istisnali():
    with Izci():
        raise ValueError("ornek")


def baglam_yutan():
    with Yutan():
        raise ValueError("ornek")


BICIMLER = (
    ("with n", baglam),
    ("with n + istisna", baglam_istisnali),
    ("with yutan + istisna", baglam_yutan),
)

print(f"{'sözdizim':<20s} {'çağrı':>6s}  protokol dizisi")
for ad, islev in BICIMLER:
    c = olc(islev)
    print(f"{ad:<20s} {len(c):6d}  {' '.join(c)}")

print()
print(f"{'sınıf':<10s}{'__exit__ dönüşü':<18s}istisna dışarı çıktı mı")
for ad, sinif in (("Izci", Izci), ("Yutan", Yutan)):
    n = sinif()
    donus = n.__exit__(ValueError, ValueError("ornek"), None)
    KAYIT.clear()
    try:
        with sinif():
            raise ValueError("ornek")
    except ValueError:
        cikti = "evet"
    else:
        cikti = "hayır"
    print(f"  {ad:<8s}{str(donus):<18s}{cikti}")

print()
GORULEN.clear()
with Tanik("istisnasız"):
    pass
try:
    with Tanik("istisnalı"):
        raise ValueError("ölçüm kapsam dışı")
except ValueError:
    pass
print(f"{'tanık':<14s}{'gördüğü tür':<14s}{'gördüğü değer':<22s}iz nesnesi var mı")
for ad, tur, deger, iz in GORULEN:
    print(f"  {ad:<12s}{tur:<14s}{deger:<22s}{'evet' if iz else 'hayır'}")
```

```
sözdizim              çağrı  protokol dizisi
with n                    2  __enter__ __exit__
with n + istisna          3  __enter__ __exit__ !ValueError
with yutan + istisna      2  __enter__ __exit__

sınıf     __exit__ dönüşü   istisna dışarı çıktı mı
  Izci    False             evet
  Yutan   True              hayır

tanık         gördüğü tür   gördüğü değer         iz nesnesi var mı
  istisnasız  -             -                     hayır
  istisnalı   ValueError    ölçüm kapsam dışı     evet
```

#### Üç Satırın Söylediği

İlk iki satır ortak kurgunun `with` satırlarıdır. **`with n` iki çağrı üretir:** `__enter__`
ve `__exit__`. **`with n + istisna` üç olay üretir:** `__enter__`, `__exit__` ve
`!ValueError`. Aradaki farkı doğru okumak gerekir — fazladan olan **bir yöntem çağrısı
değildir**. Her iki biçimde de `__enter__` bir kez, `__exit__` bir kez çağrıldı. Fazladan olan
şey, biçimin **istisnayla bitmesidir**.

Bu, dersin merkez cümlesini veriyor: **istisna oluştuğunda `__exit__` yine çağrılır.** Blok
yarıda kesildi, kalan satırlar çalışmadı, denetim `with` deyiminden dışarı fırladı — ve
`__exit__` yine de çalıştı. Kaynağın kapanmasını sağlayan şey budur ve `with` yazmanın
gerekçesi de budur.

Üçüncü satır aynı istisnayı `Yutan` ile deniyor ve çağrı sayısı **2**'ye iniyor. Dizi yine
`__enter__ __exit__` — yöntem çağrıları değişmedi. Değişen tek şey, kayda `!ValueError`
düşmemesi: istisna **dışarı çıkmadı**. Orta tablo bunu doğruluyor. `Izci`'nin `__exit__`
dönüşü `False` ve istisna dışarı **çıktı**; `Yutan`'ın dönüşü `True` ve **çıkmadı**. Tek
satırlık bir dönüş değeri, üç olaylık bir biçimi iki olaya indirdi.

Son tablo `__exit__`'in **ne gördüğünü** veriyor. İstisnasız blokta üç argümanın da boş
olduğu görülüyor: tür `-`, değer `-`, iz **hayır**. İstisnalı blokta tür `ValueError`, değer
istisnanın iletisi ve iz nesnesi **var**. `__exit__` istisnayı yalnız yutup yutmamakla
kalmaz; **hangi istisna olduğunu bilir** ve kararını ona bakarak verebilir. Belli bir sınıfı
yutup ötekileri geçiren bir bağlam yöneticisi bu üçlüyle yazılır.

#### İç İçe Bloklar ve try/finally

İkinci ölçüm iki soruyu birden yanıtlar. Birincisi: iki bağlam yöneticisi iç içe olduğunda
istisnayı hangisi görür ve içteki yutarsa dıştaki ne görür? İkincisi: `with` ile `try/finally`
arasındaki fark ölçülebilir mi?

- **HD36** — İç içe ölçümde dört birleşim denenir: dıştaki ve içteki yönetici ayrı ayrı yutan
  ya da yutmayan olur. Başka hiçbir şey değişmez.
- **HD37** — "Gördü" sütunu `__exit__`'e geçen tür argümanından okunur; boş geçildiyse `-`
  yazılır.
- **HD38** — Çıkış biçimi tablosunda dört yol denenir: normal bitiş, `return`, `break` ve
  istisna. `return` ve `break` için blok bir döngü ve bir işlev içine konur, çünkü ikisi de
  ancak orada anlamlıdır.
- **HD39** — `with` kolu ile `try/finally` kolu **aynı dört yolu** dener; aralarındaki tek
  fark, birinde bağlam yöneticisi, ötekinde bir `finally` bloğu olmasıdır.

```python
"""Ic ice with, cikis bicimleri ve try/finally ile karsilastirma."""

KAYIT = []
GORULEN = []


def kaydet(ad):
    KAYIT.append(ad)


class Izci:
    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __enter__(self):
        kaydet("__enter__")
        return self

    def __exit__(self, tur, deger, iz):
        kaydet("__exit__")
        return False


class Tanik(Izci):
    """Adiyla kaydeder ve yutup yutmayacagini kurulusta alir."""

    def __init__(self, ad, yutar=False, ogeler=(1, 2, 3)):
        super().__init__(ogeler)
        self.ad = ad
        self.yutar = yutar

    def __exit__(self, tur, deger, iz):
        GORULEN.append((self.ad, tur.__name__ if tur else "-"))
        super().__exit__(tur, deger, iz)
        return self.yutar


print(f"{'dış':<10s}{'iç':<9s}{'çağrı':>6s}  {'dizi':<40s}"
      f"{'dış gördü':<12s}{'iç gördü':<12s}istisna")
for dis_yutar in (False, True):
    for ic_yutar in (False, True):
        KAYIT.clear()
        GORULEN.clear()
        try:
            with Tanik("dış", dis_yutar), Tanik("iç", ic_yutar):
                raise ValueError("ölçüm kapsam dışı")
        except ValueError:
            sonuc = "çıktı"
        else:
            sonuc = "yutuldu"
        gorulen = dict(GORULEN)
        print(f"  {('yutar' if dis_yutar else 'yutmaz'):<8s}"
              f"{('yutar' if ic_yutar else 'yutmaz'):<9s}{len(KAYIT):6d}  "
              f"{' '.join(KAYIT):<40s}{gorulen['dış']:<12s}"
              f"{gorulen['iç']:<12s}{sonuc}")

print()
FINALLY = []


def with_ile(bicim):
    KAYIT.clear()
    GORULEN.clear()
    for _ in range(1):
        with Tanik("tek"):
            if bicim == "return":
                return
            if bicim == "break":
                break
            if bicim == "istisna":
                raise ValueError("ölçüm kapsam dışı")


def finally_ile(bicim):
    FINALLY.clear()
    for _ in range(1):
        try:
            if bicim == "return":
                return
            if bicim == "break":
                break
            if bicim == "istisna":
                raise ValueError("ölçüm kapsam dışı")
        finally:
            FINALLY.append("finally")


print(f"{'çıkış biçimi':<16s}{'__exit__ çağrıldı':<19s}{'finally çalıştı':<17s}"
      f"__exit__ istisnayı gördü")
for bicim in ("normal", "return", "break", "istisna"):
    try:
        with_ile(bicim)
    except ValueError:
        pass
    cagrildi = "evet" if "__exit__" in KAYIT else "hayır"
    gordu = "evet" if GORULEN and GORULEN[0][1] != "-" else "hayır"
    try:
        finally_ile(bicim)
    except ValueError:
        pass
    calisti = "evet" if FINALLY else "hayır"
    print(f"  {bicim:<14s}{cagrildi:<19s}{calisti:<17s}{gordu}")
```

```
dış       iç        çağrı  dizi                                    dış gördü   iç gördü    istisna
  yutmaz  yutmaz        4  __enter__ __enter__ __exit__ __exit__   ValueError  ValueError  çıktı
  yutmaz  yutar         4  __enter__ __enter__ __exit__ __exit__   -           ValueError  yutuldu
  yutar   yutmaz        4  __enter__ __enter__ __exit__ __exit__   ValueError  ValueError  yutuldu
  yutar   yutar         4  __enter__ __enter__ __exit__ __exit__   -           ValueError  yutuldu

çıkış biçimi    __exit__ çağrıldı  finally çalıştı  __exit__ istisnayı gördü
  normal        evet               evet             hayır
  return        evet               evet             hayır
  break         evet               evet             hayır
  istisna       evet               evet             evet
```

#### Sayıların Okunması

İç içe tablonun **dört** satırında da çağrı sayısı **4** ve dizi aynı:
`__enter__ __enter__ __exit__ __exit__`. Giriş dıştan içe, çıkış içten dışa. Yutma kararı
**hiçbir çağrıyı iptal etmiyor** — dört yöntem dört birleşimde de çalışıyor.

Değişen şey `gördü` sütunlarıdır. İçteki yönetici **dört satırda da** `ValueError` görüyor;
istisna ona en yakın yerde oluşuyor ve önce ona geliyor. Dıştaki ise içteki **yutmadığında**
görüyor, yuttuğunda `-` alıyor. İkinci ve dördüncü satırlar bunu gösteriyor: içteki yuttuysa
dıştaki için ortada bir istisna kalmamıştır ve `__exit__` boş bir üçlüyle çağrılır.

Üçüncü satır tersini gösteriyor: içteki yutmadı, dıştaki gördü ve yuttu. İstisna dışarı
çıkmadı. Yutma yetkisi **yukarı doğru sırayla** dolaşır ve ilk yutan zinciri keser.

İkinci tablo `with` ile `try/finally` arasındaki ortak yanı ve farkı ayırıyor. Ortak yan
şudur: dört çıkış biçiminin **hepsinde** hem `__exit__` çağrılıyor hem `finally` çalışıyor —
blok hangi yolla biterse bitsin temizlik kodu çalışır. Bu yüzden `with` bir "hata yakalama"
aracı değildir; **çıkış aracıdır**
ve dört yolun dördünde de devrededir.

Fark son sütundadır. `__exit__` yalnız istisna biçiminde **istisnayı görüyor**; öbür üç
biçimde boş üçlüyle çağrılıyor. `finally` bloğu ise hiçbir biçimde bir üçlü almaz — çalıştığı
sırada hangi istisnanın uçtuğunu bilmez ve ona bakarak karar veremez. `finally` içinde
istisnayı yutmanın tek yolu oradan yeni bir deyimle çıkmaktır ve bu, blokta ne olduğuna
bakmadan **her** istisnayı yutar. `__exit__` ise türe bakıp seçebilir.

Seçim buradan çıkar: temizlik tek bir yerde bir kez gerekiyorsa `finally` yeter. Aynı temizlik
birden çok yerde tekrarlanıyorsa ya da karar istisnanın **türüne** bağlıysa protokol yazılır.

#### Bağlanan Nesne ve Temizliğin Kendi Hatası

İki ayrıntı kaldı ve ikisi de protokolün kenarında duruyor. Birincisi `as` ile bağlanan
addır: bu ad **yöneticinin kendisi değil**, `__enter__`'ın döndürdüğü şeydir. İkincisi
`__exit__`'in kendi kırılmasıdır — temizlik de başarısız olabilir.

- **HD40** — `Kapak` yalnız `__enter__`'ın dönüşünü değiştirir; kaydı üst sınıftan alır.
  `Kirilan` yalnız temizlik sırasında bir istisna ekler.
- **HD41** — Bağlanan nesnenin kimliği `is` ile sınanır; **kimlik sayısı basılmaz**.
- **HD42** — Zincir uzunluğu birinci dersteki ölçümle **aynı yöntemle** sayılır: bastırılmamış
  bağlam ve neden bağları izlenir.

```python
"""__enter__'in donusu ve __exit__ icinde olusan istisna."""

KAYIT = []


class Izci:
    def __init__(self, ogeler=(1, 2, 3)):
        self.ogeler = list(ogeler)

    def __enter__(self):
        KAYIT.append("__enter__")
        return self

    def __exit__(self, tur, deger, iz):
        KAYIT.append("__exit__")
        return False


class Kapak(Izci):
    """__enter__ kendini degil, blogun kullanacagi baska bir nesne dondurur."""

    def __enter__(self):
        super().__enter__()
        return self.ogeler


class Kirilan(Izci):
    """Temizlik sirasinda kendisi de bir istisna uretir."""

    def __exit__(self, tur, deger, iz):
        super().__exit__(tur, deger, iz)
        raise RuntimeError("kaynak kapatılamadı")


n = Izci()
with n as bagli:
    pass
print("Izci: as ile bağlanan nesne yöneticinin kendisi mi:", bagli is n)

k = Kapak()
with k as bagli:
    pass
print("Kapak: as ile bağlanan nesne yöneticinin kendisi mi:", bagli is k)
print("Kapak: as ile bağlanan nesne __enter__'in döndürdüğü mü:",
      bagli is k.ogeler)

print()
print(f"{'durum':<36s}{'dışarı çıkan':<15s}{'bağlam':<14s}zincir")
for ad, sinif, firlat in (("blok temiz, __exit__ temiz", Izci, False),
                          ("blok istisnalı, __exit__ temiz", Izci, True),
                          ("blok temiz, __exit__ kırılan", Kirilan, False),
                          ("blok istisnalı, __exit__ kırılan", Kirilan, True)):
    try:
        with sinif():
            if firlat:
                raise ValueError("ölçüm kapsam dışı")
    except BaseException as d:
        cikan = type(d).__name__
        baglam = type(d.__context__).__name__ if d.__context__ else "-"
        uzunluk, halka = 0, d
        while halka is not None:
            uzunluk += 1
            halka = halka.__cause__ or (None if halka.__suppress_context__
                                        else halka.__context__)
    else:
        cikan = baglam = "-"
        uzunluk = 0
    print(f"  {ad:<34s}{cikan:<15s}{baglam:<14s}{uzunluk}")
```

```
Izci: as ile bağlanan nesne yöneticinin kendisi mi: True
Kapak: as ile bağlanan nesne yöneticinin kendisi mi: False
Kapak: as ile bağlanan nesne __enter__'in döndürdüğü mü: True

durum                               dışarı çıkan   bağlam        zincir
  blok temiz, __exit__ temiz        -              -             0
  blok istisnalı, __exit__ temiz    ValueError     -             1
  blok temiz, __exit__ kırılan      RuntimeError   -             1
  blok istisnalı, __exit__ kırılan  RuntimeError   ValueError    2
```

İlk üç satır `as` bağlamasını çözüyor. `Izci` kendini döndürdüğü için bağlanan ad
yöneticinin kendisi. `Kapak` başka bir nesne döndürüyor ve bağlanan ad artık yönetici
**değil**. Bir dosyayı `with` ile açtığınızda `as` ile bağlanan şeyin neden dosya nesnesi
olduğunu bu açıklar: `__enter__` onu döndürür.

Alt tablo temizliğin kendi hatasını sayıyor ve birinci dersin zincir ölçümüyle birleşiyor.
Blok da `__exit__` de temizse dışarı hiçbir şey çıkmıyor, zincir **0**. Yalnız blok
kırıldığında `ValueError` çıkıyor, zincir **1**. Yalnız `__exit__` kırıldığında `RuntimeError`
çıkıyor, zincir yine **1**.

Son satır tehlikeli olanıdır. Blok `ValueError` fırlatmış, `__exit__` çağrılmış ve o da
kırılmış. Dışarı çıkan **`RuntimeError`** — yani **temizliğin hatası**, alanın hatasının
yerine geçiyor. Asıl hata kaybolmuyor: `__context__` alanında duruyor ve zincir **2**. Ama
dışarıdaki `except ValueError` klozu artık tutmaz, çünkü uçan sınıf değişti. Bu yüzden
`__exit__` içindeki iş **kırılmamalıdır**; kırılabilecekse kendi içinde ele alınmalıdır.

#### Özet

- `with` bir kısaltmadır: girerken `__enter__`, çıkarken `__exit__` çağırır ve protokolü
  tanımlamayan nesne `with` deyimine giremez.
- İstisnasız blok **2** olay üretir, istisnalı blok **3**; fazladan olan bir yöntem çağrısı
  değil, biçimin istisnayla bitmesidir — `__exit__` her ikisinde de **bir kez** çağrılır.
- `__exit__` üç argüman alır ve istisnalı blokta türü, nesneyi ve izi görür; dönüş değeri
  doğru sayıldığında istisna yutulur ve olay sayısı **3**'ten **2**'ye iner.
- İç içe iki yöneticide çağrı sayısı dört birleşimde de **4**'tür; içteki istisnayı hep görür,
  dıştaki ancak içteki yutmadığında görür.
- `__exit__` ve `finally` dört çıkış biçiminin hepsinde çalışır; ayrıldıkları yer, `__exit__`
  istisnanın **türünü görüp** seçebilirken `finally`'nin görememesidir.
- `as` ile bağlanan ad yöneticinin kendisi değil `__enter__`'ın dönüşüdür; `__exit__` kendi
  kırılırsa dışarı **temizliğin hatası** çıkar, asıl hata bağlamda kalır ve zincir **2** olur.

#### Sonraki Adım

Bu kursun ölçtüğü son sözdizim biçimi geride kaldı. Şimdiye kadar her ölçüm, dilin kendi
protokollerini çağıran bir deyim ya da ifadeydi: `for`, `if`, `+`, `with`. Sonraki dersin
konusu ise dilin sözdizimi değil, **standart kitaplığın** sunduğu ayrı bir küçük dildir:
düzenli ifadeler. Orada örüntü bir dizgide yazılır, ayrı bir kural kümesine göre yorumlanır ve
eşleşme bir nesne olarak geri döner. Ölçülecek şey o nesnenin ne taşıdığı ve örüntüdeki
parantezlerin hangi sırayla numaralandığıdır.

### Düzenli İfadeler

Source: https://academia.sh/tr/kurslar/python-temelleri/duzenli-ifadeler

Örüntüdeki yedi açılış parantezinin ikisi yakalamaz ve geriye beş numaralı grup kalır; numara açılış parantezinin sırasına göre verilir, eşleşmeme bir istisna değil None'dır ve eşleşme nesnesi her grubun metnini, aralığını ve adını taşır.

Önceki ders bu kursun ölçtüğü son sözdizim biçimini geride bıraktı. Şimdiye kadar her ölçüm
dilin kendi protokollerini çağıran bir deyim ya da ifadeydi: `for`, `if`, `+`, `with`.

Bu dersin konusu ise dilin sözdizimi değildir. **Düzenli ifade (regular expression)** standart
kitaplığın sunduğu ayrı bir küçük dildir: örüntü bir dizgide yazılır, ayrı bir kural kümesine
göre yorumlanır ve sonuç bir **nesne** olarak geri döner. Örüntü eşleme algoritmalarının
kendisi — kaba kuvvet arama, karma tabanlı arama, önek tablolu arama — **Algoritmalar**
kursunun metin algoritmaları konusunda kuruldu ve burada tekrarlanmaz. Ölçülecek olan şey
başka: **eşleşme nesnesinin ne taşıdığı** ve örüntüdeki parantezlerin **hangi sırayla
numaralandığı**.

#### Örüntüde Parantezin İki İşi

Bir örüntüde parantezin iki ayrı işi vardır ve ikisi aynı yazımla görünür. Birincisi
**gruplama**: içindeki parçayı tek bir birim yapar ki tekrar ya da seçim işleci ona
uygulanabilsin. İkincisi **yakalama**: eşleşen metni ayrıca saklar ve numaralar.

Sıradan parantez **her iki işi birden** yapar. `(?:` ile açılan parantez yalnız gruplar,
yakalamaz — numara almaz. `(?P<ad>` ile açılan parantez hem yakalar hem numarasının yanına
bir ad koyar.

Numaralama kuralı tek cümledir: **numara, açılış parantezinin soldan sağa sırasına göre
verilir.** İç içe parantezlerde bile dıştaki önce numaralanır, çünkü onun açılışı daha
soldadır. Kapanış sırası hiç bakılmaz.

#### Grupların Numaralanması

Ölçüm bir ölçüm kaydı satırını çözen tek bir örüntü kurar: istasyon adı, yıl, isteğe bağlı ay,
değer ve birim. Örüntü hem adlı hem yakalamayan parantez içerir.

- **HD43** — Parantez sayıları örüntü dizgisinin kendisinden sayılır; numaralanan grup sayısı
  ise derlenmiş örüntünün kendi alanından okunur. İkisi ayrı kaynaktır ve birbirini
  doğrular.
- **HD44** — Kâhin örüntüyü yazan kurgudur: hangi parçanın hangi gruba düşmesi gerektiği
  önceden bilinir.
- **HD45** — Aralıklar eşleşme nesnesinden okunur, elle sayılmaz; **karakter** aralığıdır,
  bayt değil.
- **HD46** — İsteğe bağlı grup eşleşmediğinde `None` döner; bu bir hata değil, grubun
  katılmadığının bildirimidir.
- **HD47** — İç içe örüntü ölçümü yalnız numaralamayı sınamak için ayrı tutulur; başka hiçbir
  şey ölçmez.

```python
"""Yakalama gruplari: parantezlerin numaralanmasi ve eslesme nesnesinin tasidigi."""
import re

ORUNTU = r"(?P<istasyon>[a-z]+)-(\d{4})(?:-(\d{2}))?:\s*(-?\d+(?:[.,]\d+)?)\s*(°C|%)"
DERLI = re.compile(ORUNTU)

print(f"örüntüdeki açılış parantezi {ORUNTU.count('(')} | "
      f"yakalamayan parantez {ORUNTU.count('(?:')} | "
      f"adlı grup {ORUNTU.count('(?P<')} | numaralanan grup {DERLI.groups}")
print("ad -> numara:", DERLI.groupindex)

SATIRLAR = ("kuzey-2031: 12,5 °C", "yamac-2031-07: -3.5 °C",
            "ozet-2031: 88 %", "bozuk kayit")

print()
print(f"{'satır':<26s}{'eşleşti':<9s}{'aralık':<10s}{'son grup':>9s}   gruplar")
for s in SATIRLAR:
    e = DERLI.search(s)
    if e is None:
        print(f"  {s:<24s}{'hayır':<9s}{'-':<10s}{'-':>9s}   -")
    else:
        print(f"  {s:<24s}{'evet':<9s}{str(e.span()):<10s}"
              f"{e.lastindex:>9d}   {e.groups()}")

print()
e = DERLI.search(SATIRLAR[1])
print("ikinci satırın eşleşme nesnesi:")
print(f"  group(0) (tüm eşleşme): {e.group(0)!r}")
for i in range(1, DERLI.groups + 1):
    print(f"  group({i}): {e.group(i)!r}  aralık {e.span(i)}")
print(f"  adla erişim: {e.group('istasyon')!r} | "
      f"adlı grup sözlüğü: {e.groupdict()}")

print()
IC_ICE = re.compile(r"((\d{4})-(\d{2}))")
i = IC_ICE.search("yamac-2031-07: -3.5 °C")
print(f"iç içe örüntüde numaralanan grup {IC_ICE.groups}; "
      f"numara açılış parantezinin sırasına göre verilir:")
for n in range(1, IC_ICE.groups + 1):
    print(f"  group({n}) = {i.group(n)!r}")
```

```
örüntüdeki açılış parantezi 7 | yakalamayan parantez 2 | adlı grup 1 | numaralanan grup 5
ad -> numara: {'istasyon': 1}

satır                     eşleşti  aralık     son grup   gruplar
  kuzey-2031: 12,5 °C     evet     (0, 19)           5   ('kuzey', '2031', None, '12,5', '°C')
  yamac-2031-07: -3.5 °C  evet     (0, 22)           5   ('yamac', '2031', '07', '-3.5', '°C')
  ozet-2031: 88 %         evet     (0, 15)           5   ('ozet', '2031', None, '88', '%')
  bozuk kayit             hayır    -                 -   -

ikinci satırın eşleşme nesnesi:
  group(0) (tüm eşleşme): 'yamac-2031-07: -3.5 °C'
  group(1): 'yamac'  aralık (0, 5)
  group(2): '2031'  aralık (6, 10)
  group(3): '07'  aralık (11, 13)
  group(4): '-3.5'  aralık (15, 19)
  group(5): '°C'  aralık (20, 22)
  adla erişim: 'yamac' | adlı grup sözlüğü: {'istasyon': 'yamac'}

iç içe örüntüde numaralanan grup 3; numara açılış parantezinin sırasına göre verilir:
  group(1) = '2031-07'
  group(2) = '2031'
  group(3) = '07'
```

#### Eşleşme Nesnesinin Taşıdığı

İlk satır aritmetiği veriyor: örüntüde **7** açılış parantezi var, **2**'si yakalamayan, geriye
**5** numaralanan grup kalıyor. Sayının biri örüntü dizgisinden, öbürü derlenmiş örüntüden
geldi ve tuttular. Adlı grubun numarası kaybolmuyor: `istasyon` adı **1** numaraya karşılık
geliyor. Ad bir takma addır, numaranın yerine geçmez.

İkinci tablo dört satırı çözüyor. Üçünde eşleşme var, birinde yok. Eşleşenlerin `gruplar`
sütunu beşerli demet veriyor ve isteğe bağlı ay grubu, ay yazılmamış satırlarda `None`
dönüyor. `None` burada bir hata değildir: grubun **katılmadığı** anlamına gelir ve boş
dizgiden ayrılır — boş dizgi "eşleşti ama içi boştu" demek olurdu.

Üçüncü blok eşleşme nesnesinin taşıdığı bilginin tamamını açıyor. `group(0)` **tüm eşleşmedir**
ve bir grup değildir; numaralar ondan sonra başlar. Her grup hem metnini hem **aralığını**
veriyor: `2031` kaydın **6**–**10** karakterleri arasında duruyor. Bu aralık, eşleşen metni
kaynağın içinde yerinden çıkarmadan işaret etmeye yarar.

Son blok numaralamayı kesinleştiriyor. İç içe örüntüde **3** grup var ve `group(1)` dıştaki
bütünü, `group(2)` yılı, `group(3)` ayı veriyor. Dıştaki parantez sonra kapanmasına rağmen
**önce** numaralanıyor, çünkü **önce açılıyor**.

#### Aramanın Biçimleri

İkinci ölçüm aynı örüntüyü ayrı arama biçimleriyle çalıştırır ve eşleşmemenin ne ürettiğini
sınar.

- **HD48** — Üç arama biçimi **aynı** derlenmiş örüntüyle ve aynı satırlarla denenir; tek
  değişen çağrılan yöntemdir.
- **HD49** — Çoklu arama sayıları listenin uzunluğundan okunur; satır sayısı metnin kendi
  satırlarından sayılır.
- **HD50** — Eşleşmeme durumunda dönen değer doğrudan basılır; sonucun istisna olmadığı
  koşumla gösterilir.
- **HD51** — Konum ölçümü aynı içeriği bir kez dizgi, bir kez bayt dizisi olarak arar; fark
  yalnız içeriğin türünden gelir.

```python
"""Eslesme aramanin bicimleri, eslesmeme durumu ve dizgi ile bayt uzerinde konum."""
import re

ORUNTU = r"(?P<istasyon>[a-z]+)-(\d{4})(?:-(\d{2}))?:\s*(-?\d+(?:[.,]\d+)?)\s*(°C|%)"
DERLI = re.compile(ORUNTU)

DENEMELER = ("kuzey-2031: 12,5 °C", "  kuzey-2031: 12,5 °C",
             "kuzey-2031: 12,5 °C ek", "bozuk kayit")

print(f"{'satır':<28s}{'search':<9s}{'match':<8s}fullmatch")
for s in DENEMELER:
    d = [("evet" if a(s) else "hayır")
         for a in (DERLI.search, DERLI.match, DERLI.fullmatch)]
    print(f"  {repr(s):<26s}{d[0]:<9s}{d[1]:<8s}{d[2]}")

KAYITLAR = ("kuzey-2031: 12,5 °C\n"
            "yamac-2031-07: -3.5 °C\n"
            "ozet-2031: 88 %\n"
            "bozuk kayit\n"
            "guney-2032: 41 °C")

print()
bulunan = DERLI.findall(KAYITLAR)
yinelenen = list(DERLI.finditer(KAYITLAR))
print(f"metin satırı {len(KAYITLAR.splitlines())} | findall {len(bulunan)} | "
      f"finditer {len(yinelenen)} | findall öğesinin uzunluğu "
      f"{len(bulunan[0])} | grup sayısı {DERLI.groups}")
print("finditer aralıkları:", [e.span() for e in yinelenen])

print()
yok = DERLI.search("bozuk kayit")
print("eşleşmeyen aramanın sonucu:", yok, "| istisna fırlattı mı: hayır")
try:
    yok.group(0)
except AttributeError as e:
    print(f"sonuç üzerinde group çağrısı: {type(e).__name__} — "
          f"birinci dersin sınıflamasında program kusuru")

print()
print("değiştirme, grupları geri kullanır:")
print(" ", DERLI.sub(r"\g<istasyon>=\4\5", KAYITLAR).replace("\n", " | "))

print()
METIN = "kuzey yamaç: 12,5 °C"
HAM = METIN.encode("utf-8")
print(f"karakter {len(METIN)} | bayt {len(HAM)}")
print(f"dizgi üzerinde 'C' konumu {re.search(r'C', METIN).start()} | "
      f"bayt dizisi üzerinde {re.search(rb'C', HAM).start()}")
```

```
satır                       search   match   fullmatch
  'kuzey-2031: 12,5 °C'     evet     evet    evet
  '  kuzey-2031: 12,5 °C'   evet     hayır   hayır
  'kuzey-2031: 12,5 °C ek'  evet     evet    hayır
  'bozuk kayit'             hayır    hayır   hayır

metin satırı 5 | findall 4 | finditer 4 | findall öğesinin uzunluğu 5 | grup sayısı 5
finditer aralıkları: [(0, 19), (20, 42), (43, 58), (71, 88)]

eşleşmeyen aramanın sonucu: None | istisna fırlattı mı: hayır
sonuç üzerinde group çağrısı: AttributeError — birinci dersin sınıflamasında program kusuru

değiştirme, grupları geri kullanır:
  kuzey=12,5°C | yamac=-3.5°C | ozet=88% | bozuk kayit | guney=41°C

karakter 20 | bayt 22
dizgi üzerinde 'C' konumu 19 | bayt dizisi üzerinde 21
```

#### Sayıların Okunması

İlk tablo üç arama biçimini ayırıyor. `search` örüntüyü **her yerde** arar ve dört satırın
üçünde buluyor. `match` yalnız **baştan** dener: önünde boşluk olan satırda başarısız oluyor.
`fullmatch` satırın **tamamının** eşleşmesini ister: sonunda fazladan metin olan satırda
başarısız oluyor. Aynı örüntü, aynı satır, üç ayrı yanıt — hangi soruyu sorduğunuza bağlı.

İkinci satır çoklu aramayı sayıyor: **5** satırlık metinde **4** eşleşme var ve `findall` ile
`finditer` aynı sayıyı veriyor. Fark taşıdıkları şeydedir. `findall` her eşleşme için **5**
uzunluğunda bir demet veriyor — yani yalnız grupların metnini; aralık ve ad bilgisi kayboluyor.
`finditer` ise eşleşme nesnelerinin kendisini veriyor ve aralıklar okunabiliyor. Grup varken
`findall` bir sadeleştirmedir ve sadeleştirdiği şey, önceki bölümde ölçülen bilginin tamamıdır.

Üçüncü blok eşleşmemenin ne olduğunu söylüyor: sonuç **`None`** ve **istisna yok**. Düzenli
ifade eşleşmemeyi bir hata saymaz, çünkü eşleşmeme beklenen bir sonuçtur. İstisna ancak
`None` üzerinde `group` çağrılırsa doğar ve o zaman gelen sınıf `AttributeError` olur —
birinci dersin sınıflamasında bir **program kusuru**, yani `except` ile ele alınacak değil,
sınamayla önlenecek bir şey. Arama sonucu kullanılmadan önce sınanır.

Değiştirme satırı grupların ikinci işini gösteriyor: yakalanan parçalar yerine konan metinde
**numarayla ya da adla** geri kullanılabiliyor. Eşleşmeyen satır olduğu gibi kalıyor.

Son iki satır bu kursun üçüncü dersine bağlanıyor. Aynı içerik dizgi olarak **20** karakter,
bayt dizisi olarak **22** bayt; aranan harfin konumu birinde **19**, ötekinde **21**. Düzenli
ifade dizgide **karakter**, bayt dizisinde **bayt** sayar. Örüntü ile arandığı şeyin türü aynı
olmalıdır ve dönen aralıklar o türün birimindedir.

#### Özet

- Parantezin iki işi vardır: gruplama ve yakalama; `(?:` yalnız gruplar, `(?P<ad>` hem yakalar
  hem ad verir — örüntüdeki **7** açılış parantezinin **2**'si yakalamaz, geriye **5** grup kalır.
- Numara **açılış parantezinin sırasına** göre verilir; iç içe örüntüde dıştaki grup önce
  numaralanır ve `group(0)` bir grup değil, tüm eşleşmedir.
- Eşleşme nesnesi her grubun metnini, **aralığını** ve varsa adını taşır; katılmayan isteğe
  bağlı grup `None` döner ve bu boş dizgiden ayrı bir bildirimdir.
- `search`, `match` ve `fullmatch` aynı satırda ayrı yanıt verir; `findall` **5** öğelik
  demetler döndürüp aralık bilgisini düşürür, `finditer` eşleşme nesnesini korur.
- Eşleşmeme bir istisna değil `None`'dır; istisna ancak sınanmadan `group` çağrılırsa doğar ve
  `AttributeError` olarak gelir.

#### Kurs Kapanışı

Bu kurs tek bir iddiayı on altı derste ölçtü: **Python'da sözdizim bir kısaltmadır.** Yazılan
her biçim, adı belli bir **özel yöntemi** ya da adı belli bir kuralı çağırır; bir dersin sayısı,
anlattığı sözdizimin hangi protokolü kaç kez çağırdığıdır.

| ders | anlatılan sözdizim | çağrılan protokol |
|---|---|---|
| Python'un Yürütme Modeli | on bir sözdizim biçimi | 12 özel yöntem, toplam 18 çağrı |
| Sözdizim ve Girinti | girinti, blok, deyim ve ifade | özel yöntem yok: girinti birimleri ve ağaç |
| Değişkenler ve Veri Tipleri | ad bağlama, `x += ek` | `__add__` ile `__iadd__` ayrımı, nesne kimliği |
| İşleçler | `n + m`, `n += m`, `x in n` | `__add__`, `__iadd__`, `__contains__` yoksa `__iter__` |
| Dizgilerle Çalışma | dizgi yöntemleri ve biçimlendirme yazımları | `__format__`, `__str__`, `__repr__`; değiştirilemezlik yeni nesne kurdurur |
| Tip Dönüşümü | `int(n)`, `float(n)`, `str(n)`, dizin olarak kullanım | `__int__`, `__float__`, `__str__`, `__index__`; yöntem yoksa `TypeError` |
| Koşullar | `if n` | `__bool__`, yoksa `__len__`, o da yoksa doğru |
| Döngüler | `for x in n` | `__iter__` ve `__next__`; bitişi `StopIteration` verir |
| Fonksiyon Tanımı | `def`, varsayılan değer, argüman biçimleri | varsayılan tanım anında bir kez değerlendirilir |
| Kapsam Kuralları | ad okuma ve atama | yerel, kapsayan, genel, yerleşik dört düzeyli arama |
| Yerleşik Fonksiyonlar | `len`, `str`, `iter`, `bool`, `sorted` ve üç yerleşik daha | sekiz yerleşik, toplam 21 çağrı; `bool` için `__bool__` yoksa `__len__` |
| İstisnalar | `try`, `except`, `raise`, `finally` | istisna hiyerarşisi; yakalama sınıf soyağacına bakar |
| Özel İstisnalar | `class ...(Exception)` | sınıfın hiyerarşideki yeri yakalama davranışını belirler |
| Dosya İşlemleri | `open` metin kipi ve ikili kip | kodlama çözücüsü ve satır sonu çevirisi |
| Bağlam Yöneticileri | `with n` | `__enter__`, `__exit__` ve dönüş değerinin yutma kararı |
| Düzenli İfadeler | örüntü, grup, değiştirme | eşleşme nesnesi ve yakalama grubu numaralaması |

İkinci sütunla üçüncü sütun arasındaki mesafe kursun bütün bulgusudur. Aynı görünen iki
yazım ayrı protokol çağırır — `n + m` ile `n += m` gibi. Aynı yazım, nesneye göre ayrı
protokol çağırır — `if n` gibi. Ve bir protokolün **başarısız olması** da protokolün
parçasıdır: döngüyü `StopIteration` bitirir, `with` bloğundaki istisnayı `__exit__` görür ve
dönüş değeriyle onu yutup yutmayacağına karar verir.

Buradan çıkan son okuma şudur: **bir nesnenin ne yapabildiği, tanımladığı özel yöntemlerle
belirlenir; tipinin adıyla değil.** Kurs boyunca ölçülen nesne hiçbir yerleşik koleksiyondan
inmedi; `for` içine girebilmesinin, `in` ile sınanabilmesinin, `with` bloğunda
kullanılabilmesinin tek gerekçesi ilgili yöntemleri **tanımlamış** olmasıydı.

Bu kurs boyunca protokoller hep **kullanıldı**. Bir sonraki kurs olan **Veri Yapıları ve
Fonksiyonel Araçlar** işin öteki yarısını alır: protokolü **yazmak**. İkisi ayrı şeylerdir.
`for` yazan biri yineleme protokolünün çağıranıdır; kendi yineleyicisini, kendi üretecini,
kendi sıralama ölçütünü yazan biri ise protokolün **karşı tarafını** kurar. Listeler, demetler,
sözlükler ve kümeler orada birer veri yapısı olarak değil, birer **protokol gerçekleştirimi**
olarak ele alınır — ve kendi nesnenizi onların yerine geçirebilmenizin koşulu, bu kursta
sayılan yöntemleri kendinizin tanımlamasıdır.

