---
title: 'Nesneye Dayalı Python ve Tipler'
source: 'https://academia.sh/tr/kurslar/python-nesne-ve-tipler'
language: tr
updated: '2026-08-17T04:42:23+00:00'
license: 'CC BY-SA 4.0'
lessons: 11
outcomes:
    - 'Veri modeli yöntemleriyle dilin sözdizimine uyan sınıflar yazar'
    - 'Kalıtım yerine bileşimi seçmesi gereken durumları ayırt eder'
    - 'Tip ipuçları ve statik denetleyiciyle sözleşmeleri belirginleştirir'
---

# Nesneye Dayalı Python ve Tipler

Bir tasarımın sayısı ne yaptığı değil çağrıyı kimin yanıtladığı ve sözü kimin denetlediğidir; kurs kalıtım ile bileşimi yanıt veren gövde üzerinden, tip katmanını da çalışma zamanı, denetleyici ve alan kuralının ayrı ayrı yakaladığı ihlaller üzerinden ölçer.

## Sınıflar ve Veri Modeli

Öznitelik ve yöntemlerin bağlanması, çoklu kalıtımın çözümleme sırası, adlandırmayla görünürlük bildirimi, temsil ve karşılaştırma protokolleri, öznitelik erişiminin denetlenmesi, değer nesnelerinin kısa tanımı ve nominal ile yapısal sözleşmeler.

### Sınıf ve Örnek

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/sinif-ve-ornek

Sınıf gövdesinde yazılan öznitelik üç örnekte 1 nesne olarak paylaşılır, örnek gövdesinde yazılan 3 ayrı nesne üretir; `self` bir anahtar sözcük değil, erişim anında kurulan bağlı yöntemin taşıdığı örnektir.

Veri Yapıları ve Fonksiyonel Araçlar kursu kabın bedelini saydı ve kapanışta bir sınır çizdi:
**kabı seçmekle kabı yazmak ayrı şeylerdir.** Buraya kadar ölçülen bütün protokoller dilin
verdiği nesnelerde işledi. Liste bir uzunluğa sahipti, demet bir kapta anahtar olabiliyordu,
sözlük karşılaştırılabiliyordu — hiçbiri bizim yazdığımız bir sözleşme değildi, hepsi seçilmiş
bir kabın hazır gelen özellikleriydi.

Bu kurs sınırın öbür tarafındadır: protokole **katılan tarafı biz yazarız.** Ve bir şeyi
yazmak, onun çağrıyı gerçekten yanıtladığı anlamına gelmez. Kursun tek sorusu budur:
**çağrıyı kim yanıtladı?** İlk ders sorunun en küçük hâlini kurar. Bir sınıf tanımlandığında
iki ayrı ad alanı ortaya çıkar — sınıfın kendisi ve ondan var edilen her örnek. Bir öznitelik
okunduğunda yanıt bu ikisinden hangisinden gelir ve `self` denen ad nereden doğar?

#### İki Ayrı Ad Alanı

Bir `class` deyimi çalıştığında yorumlayıcı gövdeyi **bir kez** koşturur ve gövdede oluşan
adları sınıf nesnesinin sözlüğüne yazar. Sınıf, çalışma zamanında var olan bir nesnedir;
yöntemler de o sözlüğün içindeki sıradan işlevlerdir.

Her örnek ise **kendi** sözlüğüyle gelir. `__init__` içinde `self.ad = ...` yazıldığında ad
sınıfa değil, o örneğin sözlüğüne yazılır. Bir öznitelik okunduğunda arama önce örneğin
sözlüğüne bakar, orada yoksa sınıfın sözlüğüne, sonra sınıfın atalarına. **Yanıtı ilk bulan
verir.** Ortak kurgunun `saglayan` işlevi bu aramanın sınıf tarafını yazıya döker: bir adı
çözümleme sırasında ilk hangi sınıfın taşıdığını söyler.

`__init__`'in adı da bu düzenden çıkar. Örnek çağrı yapıldığında **önce** boş bir nesne var
edilir, **sonra** `__init__` o nesne üzerinde koşar; yani `__init__` bir nesne üretmez, var
edilmiş bir nesnenin sözlüğünü doldurur. Bir şey döndürmemesinin nedeni budur — döndürecek bir
nesnesi yoktur, kendisine verilmiş bir nesnesi vardır.

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

- **SN1** — Ölçülen sınıf ortak kurgunun `Kayit` sınıfıdır; gövdesine bir sınıf özniteliği
  (`tur`) eklenmiş, `kaynak` ve `bicimle` yetenekleri değiştirilmemiştir.
- **SN2** — Üç örnek aynı sınıftan ve tek bir kavrama ile var edilir; aralarındaki tek fark
  `__init__`'e verilen addır.
- **SN3** — "Ayrı nesne" sütunu ilk iki örneğin gördüğü değeri `is` ile karşılaştırır;
  **kimlik sayısı basılmaz.**
- **SN4** — "Örnek sözlüğünde" sütunu adın kaç örneğin **kendi** sözlüğünde bulunduğunu
  sayar; sınıftan gelen adlar bu sayıya girmez.

```python
"""Oznitelik nerede duruyor: sinif govdesi ile ornek govdesi."""


class Kayit:
    """Ortak kurgunun temel sinifi; burada iki yetenegi kullanilir."""

    tur = "kayit"                       # sinif govdesinde yazili

    def __init__(self, ad):
        self.ad = ad                    # ornek govdesinde yazili

    def kaynak(self):
        return "Kayit"

    def bicimle(self):
        return f"<{self.kaynak()}>"


def saglayan(sinif, ad):
    """Bir ozniteligi hangi sinif sagliyor: cozumleme sirasinda ilk bulan."""
    for s in sinif.__mro__:
        if ad in s.__dict__:
            return s.__name__
    return None


ucu = [Kayit(f"k{i}") for i in range(3)]

print(f"{'öznitelik':<10s} {'yanıtlayan':>11s} {'ayrı nesne':>11s} "
      f"{'örnek sözlüğünde':>17s}")
for ad in ("tur", "ad"):
    ayri = getattr(ucu[0], ad) is not getattr(ucu[1], ad)
    yerel = sum(1 for u in ucu if ad in u.__dict__)
    print(f"{ad:<10s} {saglayan(Kayit, ad) or 'örnek':>11s} {str(ayri):>11s} "
          f"{yerel:>17d}")

a, b = Kayit("a"), Kayit("b")
print(f"\nbaşta        a.tur={a.tur!r} b.tur={b.tur!r} Kayit.tur={Kayit.tur!r} "
      f"a'nın sözlüğünde tur -> {'tur' in a.__dict__}")
a.tur = "özel"
print(f"a.tur atandı a.tur={a.tur!r} b.tur={b.tur!r} Kayit.tur={Kayit.tur!r} "
      f"a'nın sözlüğünde tur -> {'tur' in a.__dict__}")
del a.tur
print(f"ad silindi   a.tur={a.tur!r} b.tur={b.tur!r} Kayit.tur={Kayit.tur!r} "
      f"a'nın sözlüğünde tur -> {'tur' in a.__dict__}")
```

```
öznitelik   yanıtlayan  ayrı nesne  örnek sözlüğünde
tur              Kayit       False                 0
ad               örnek        True                 3

başta        a.tur='kayit' b.tur='kayit' Kayit.tur='kayit' a'nın sözlüğünde tur -> False
a.tur atandı a.tur='özel' b.tur='kayit' Kayit.tur='kayit' a'nın sözlüğünde tur -> True
ad silindi   a.tur='kayit' b.tur='kayit' Kayit.tur='kayit' a'nın sözlüğünde tur -> False
```

İlk tablo iki adın iki ayrı yerde durduğunu gösteriyor. `tur` okunduğunda yanıtı veren
**`Kayit`**'tir; üç örneğin üçü de aynı nesneyi görür ve **hiçbirinin** kendi sözlüğünde bu ad
yoktur — sayı **0**. `ad` okunduğunda yanıtı veren sınıf **yoktur**; üç örneğin üçü de kendi
sözlüğünde taşır, sayı **3** ve gördükleri nesneler ayrıdır.

Alt bölüm arama sırasının doğrudan sonucudur. `a.tur = "özel"` satırı sınıftaki adı
**değiştirmez**; `a`'nın sözlüğüne yeni bir giriş açar ve o giriş aramada önce bulunduğu için
sınıftakini **gölgeler**. `b` ile sınıfın kendisi olduğu gibi kalır. Örnekteki ad silindiğinde
gölge kalkar ve `a.tur` yine sınıfın verdiği değeri okumaya döner. Bir örneğe atama yapmak
sınıfa hiçbir zaman dokunmaz — **sınıfı değiştirmenin tek yolu sınıfa yazmaktır.**

#### Sınıf Gövdesi Bir Kez Koşar

Yukarıdaki ayrımın maliyet tarafı vardır ve dikkatsiz yazımda gözden kaçar: sınıf gövdesi
tanım anında **bir kez** koşar, örnek gövdesi (`__init__`) **her örnekte** koşar. Sınıf
gövdesinde `= []` yazmak tek bir liste var eder ve o listeyi bütün örnekler paylaşır.

- **SN5** — Ölçülen sınıf aynı türden iki adı bilerek iki ayrı gövdede tanımlar; adların
  dışında hiçbir farkları yoktur.
- **SN6** — Üç örnek var edilir ve her biri **kendi sırasını** iki kaba da ekler; ölçülen şey
  her örneğin sonunda kaç öğe gördüğüdür.
- **SN7** — Paylaşım `is` ile gösterilir.

```python
class Ortak:
    """Ayni turden iki adi ayri govdelerde tanimlar: biri sinifta, oteki ornekte."""

    ortak_kutu = []                     # sinif govdesi: bir kez calisir

    def __init__(self):
        self.kendi_kutusu = []          # ornek govdesi: her ornekte calisir


ucu = [Ortak() for _ in range(3)]
for i, n in enumerate(ucu):
    n.ortak_kutu.append(i)
    n.kendi_kutusu.append(i)

print(f"{'öznitelik':<14s} {'var edilen kap':>15s} {'her örneğin gördüğü':>20s} "
      f"{'paylaşılıyor':>13s}")
print(f"{'ortak_kutu':<14s} {1:15d} {str([len(n.ortak_kutu) for n in ucu]):>20s} "
      f"{str(ucu[0].ortak_kutu is ucu[2].ortak_kutu):>13s}")
print(f"{'kendi_kutusu':<14s} {3:15d} {str([len(n.kendi_kutusu) for n in ucu]):>20s} "
      f"{str(ucu[0].kendi_kutusu is ucu[2].kendi_kutusu):>13s}")
```

```
öznitelik       var edilen kap  her örneğin gördüğü  paylaşılıyor
ortak_kutu                   1            [3, 3, 3]          True
kendi_kutusu                 3            [1, 1, 1]         False
```

Üç örnek, kap başına birer ekleme yaptı. Sınıf gövdesindeki kap **1** kez var edildiği için üç
eklemenin üçü de aynı listeye gitti ve her örnek **3** öğe görüyor. Örnek gövdesindeki kap **3**
kez var edildi ve her örnek yalnız kendi eklediğini, **1** öğe görüyor. Sayılar aynı satırlardan
çıkmıştır; ayrım tek başına gövdenin yerindedir.

Buradan bir yazım kuralı çıkar: **değiştirilebilir bir değer sınıf gövdesine yazılmaz.** Sınıf
gövdesi sabitler, sayılar ve değiştirilemez değerler içindir — onlarda paylaşımın gözlenebilir
bir sonucu yoktur, çünkü kimse onları yerinde değiştiremez. Bir örneğe ait olması gereken her
durum `__init__` içinde kurulur. Sınıf özniteliğinin doğru kullanımı da bu tabloda görünür:
ilk ölçümdeki `tur` bir **varsayılan**dır ve örnek onu yalnız gerektiğinde gölgeler; üç örnek
için tek nesne yeter, çünkü kimse onu yerinde değiştirmez.

#### `self` Bir Anahtar Sözcük Değildir

Sınıf sözlüğünde duran yöntem sıradan bir işlevdir ve ilk parametresi başka bir addan farklı
değildir. Örnek üzerinden erişildiğinde araya bir adım girer: erişim, işlevi ve örneği bir
arada tutan **bağlı bir yöntem** nesnesi üretir. Çağrıda ilk argümanı yazmayışımızın nedeni
budur — o argüman erişim anında zaten bağlanmıştır.

- **SN8** — Ölçüm ilk bloğun `Kayit` sınıfını sürdürür; yeni bir tanım yapılmaz.
- **SN9** — Aynı çağrı iki yazımla yapılır: örnek üzerinden ve sınıf üzerinden, örnek elle
  geçirilerek. Ölçülen şey iki yazımın **aynı sonucu** verip vermediğidir.
- **SN10** — Nesnelerin tipi ad olarak basılır; **kimlik sayısı ve bellek verisi basılmaz.**
- **SN11** — Son satırdaki çağrı bilerek örneksiz yapılır; istisnanın **adı** kaydedilir.

```python
k = Kayit("k")
print(f"k.bicimle() -> {k.bicimle()} | Kayit.bicimle(k) -> {Kayit.bicimle(k)} | "
      f"eşit -> {k.bicimle() == Kayit.bicimle(k)}")
print(f"sınıf sözlüğündeki nesnenin tipi -> "
      f"{type(Kayit.__dict__['bicimle']).__name__}")
print(f"örnek üzerinden erişilenin tipi  -> {type(k.bicimle).__name__}")
print(f"bağlı yöntemin tuttuğu nesne k mi -> {k.bicimle.__self__ is k}")
print(f"k.bicimle is k.bicimle -> {k.bicimle is k.bicimle} | "
      f"Kayit.bicimle is Kayit.bicimle -> {Kayit.bicimle is Kayit.bicimle}")
print(f"bağlı yöntemin sardığı işlev -> {k.bicimle.__func__ is Kayit.bicimle}")

try:
    Kayit.bicimle()
except TypeError as e:
    print(f"örneksiz çağrı -> {type(e).__name__}")

print()
print(f"{'yetenek':<10s} {'yanıtlayan sınıf':>17s}")
for ad in ("kaynak", "bicimle", "__init__"):
    print(f"{ad:<10s} {saglayan(Kayit, ad):>17s}")
print(f"{'ad':<10s} {saglayan(Kayit, 'ad') or '—':>17s}")
```

```
k.bicimle() -> <Kayit> | Kayit.bicimle(k) -> <Kayit> | eşit -> True
sınıf sözlüğündeki nesnenin tipi -> function
örnek üzerinden erişilenin tipi  -> method
bağlı yöntemin tuttuğu nesne k mi -> True
k.bicimle is k.bicimle -> False | Kayit.bicimle is Kayit.bicimle -> True
bağlı yöntemin sardığı işlev -> True
örneksiz çağrı -> TypeError

yetenek     yanıtlayan sınıf
kaynak                 Kayit
bicimle                Kayit
__init__               Kayit
ad                         —
```

İlk satır iki yazımın aynı işi yaptığını gösteriyor: `k.bicimle()` ile `Kayit.bicimle(k)`
aynı sonucu veriyor. İkincisi ilkinin açılmış hâlidir. Sonraki iki satır aradaki adımı
adlandırıyor — sınıfın sözlüğünde duran nesne bir **işlev**, örnek üzerinden erişilen nesne bir
**yöntem**. Dördüncü satır yöntemin ne taşıdığını söylüyor: erişimin yapıldığı örneğin
kendisini.

Beşinci satır bağlamanın **erişim anında** olduğunu kanıtlıyor. Aynı adı iki kez okumak iki
ayrı bağlı yöntem nesnesi üretiyor; `is` karşılaştırması `False` veriyor. Sınıf üzerinden
erişimde ise bağlama yoktur — her seferinde sözlükteki aynı işlev döner ve karşılaştırma `True`
çıkar. Altıncı satır ikisini birleştiriyor: bağlı yöntemin sardığı işlev, sınıftan okunan
işlevin **ta kendisidir**. Örneksiz çağrının `TypeError` vermesinin nedeni de buradadır — işlev
bir parametre bekliyordu ve onu bağlayacak bir örnek yoktu.

Bunun günlük yazımda iki sonucu vardır. Birincisi, bağlı yöntem çağrılmadan da bir **değer**
olarak taşınabilir: `k.bicimle` bir yere geçirildiğinde yanına `k` de gitmiş olur, çünkü nesne
örneği içinde taşır. Bir geri çağrı listesine yöntem koymak, o listeye örneği de koymaktır.
İkincisi, bir yöntemi sınıftan okuyup elle çağırmak her zaman mümkündür; örnek gizli bir
argüman değil, açıkça yazılabilen ilk argümandır.

Nesneye dayalı programlamanın kavramları Programlama Temelleri kursunda kurulmuştu; **bunlar
tekrarlanmaz.** Orada sınıf bir kalıp, örnek onun bir üretimiydi. Burada ölçülen şey kalıbın
kendisinin de bir **nesne** olduğu ve yanıtın hangi sözlükten geldiğidir. Son tablo bunu
kapatıyor: dört adın dördünü de `Kayit` yanıtlıyor, `ad` özniteliğini yanıtlayan bir sınıf
**yok** — o ad hiçbir sınıfta yazılı değil, her örnekte ayrı ayrı doğuyor.

#### Bağlamanın Üç Biçimi

Bağlama bir kural olduğuna göre değiştirilebilir. Aynı sınıf gövdesinde üç ayrı bağlama biçimi
tanımlanabilir: ilk argümanı **örneğe** bağlayan yöntem, ilk argümanı **sınıfa** bağlayan
yöntem ve hiçbir şeye bağlanmayan yöntem. Üçü de aynı gövdede durur ve aynı biçimde çağrılır;
ayrıldıkları tek yer, çağrı anında ilk argümanın ne olduğudur.

- **SN12** — Üç yöntem tek bir sınıf gövdesinde tanımlanır ve üçü de aynı işi yapar: kendisine
  bağlanan şeyin adını döndürür. Bağlanan bir şey yoksa yerine bir tire döndürülür.
- **SN13** — Ölçüm bir **alt sınıf** üzerinden yapılır; gövdeleri yazan sınıf ile çağrının
  yapıldığı sınıf bilerek ayrıştırılmıştır.
- **SN14** — "Çağrı" sütunları yöntemi **argümansız** çağırır; karşılanamayan çağrı bir
  istisnayla düşer ve ünlem önekiyle **istisnanın adı** kaydedilir.
- **SN15** — Erişim sütunları dönen nesnenin tipini ad olarak basar; **kimlik sayısı
  basılmaz.**

```python
class Bicimci:
    """Ayni govdede uc baglama bicimi: ornege, sinifa ve hicbir seye baglanan."""

    def ornek_yontemi(self):
        return type(self).__name__

    @classmethod
    def sinif_yontemi(cls):
        return cls.__name__

    @staticmethod
    def duragan_yontem():
        return "—"


class AltBicimci(Bicimci):
    pass


def dene(cagri):
    try:
        return cagri()
    except TypeError as e:
        return f"!{type(e).__name__}"


n = AltBicimci()
print(f"{'yöntem':<15s} {'sınıftan erişim':>15s} {'örnekten erişim':>15s} "
      f"{'sınıftan çağrı':>15s} {'örnekten çağrı':>15s}")
for ad in ("ornek_yontemi", "sinif_yontemi", "duragan_yontem"):
    print(f"{ad:<15s} {type(getattr(AltBicimci, ad)).__name__:>15s} "
          f"{type(getattr(n, ad)).__name__:>15s} "
          f"{dene(getattr(AltBicimci, ad)):>15s} "
          f"{dene(getattr(n, ad)):>15s}")

print(f"\ngövdeyi yazan sınıf {saglayan(AltBicimci, 'sinif_yontemi')}, "
      f"cls'e bağlanan sınıf {AltBicimci.sinif_yontemi()}")
```

```
yöntem          sınıftan erişim örnekten erişim  sınıftan çağrı  örnekten çağrı
ornek_yontemi          function          method      !TypeError      AltBicimci
sinif_yontemi            method          method      AltBicimci      AltBicimci
duragan_yontem         function        function               —               —

gövdeyi yazan sınıf Bicimci, cls'e bağlanan sınıf AltBicimci
```

Tablo bağlamanın nerede olduğunu satır satır gösteriyor. Örnek yöntemi yalnız **örnekten**
erişildiğinde bağlanır; sınıftan erişilen nesne çıplak bir işlevdir ve argümansız çağrı
`TypeError` ile düşer. Sınıf yöntemi **her iki** erişimde de bağlıdır — sınıftan okunduğunda
bile bir yöntem döner, çünkü bağlanacak şey zaten sınıfın kendisidir; argümansız çağrı iki
yoldan da çalışır. Durağan yöntem hiçbir erişimde bağlanmaz; iki yoldan da çıplak işlev döner
ve iki çağrı da bağlanan bir şey olmadığını bildirir.

Altı çağrının **beşi** karşılık buluyor, **biri** düşüyor. Düşen çağrı bir eksiklik değil,
kuralın kendisidir: örnek yöntemi bir örnek olmadan çağrılamaz, çünkü ilk parametresini
dolduracak bir şey yoktur.

Son satır kursun sorusunun ilk keskin hâlidir. `sinif_yontemi` gövdesi **`Bicimci`'de**
yazılıdır; ama çağrı `AltBicimci` üzerinden yapıldığı için `cls`'e bağlanan sınıf
**`AltBicimci`**'dir. Gövdeyi yazan sınıf ile çağrıyı yanıtlayan bağlam aynı şey değildir —
gövde bir yerde durur, bağlama çağrı anında olur. Sonraki dersin ölçtüğü ayrım bunun
büyütülmüş hâlidir.

#### Özet

- Sınıf ve örnek iki ayrı ad alanıdır; öznitelik araması önce örneğin sözlüğüne, sonra sınıfın
  sözlüğüne bakar ve **yanıtı ilk bulan verir**.
- Sınıf gövdesindeki `tur` üç örnekte **1** nesne olarak paylaşılır ve hiçbirinin sözlüğünde
  bulunmaz; `__init__` içindeki `ad` **3** ayrı nesne üretir ve üç sözlüğün üçünde de durur.
- Örneğe atama sınıfı değiştirmez, gölgeler; örnekteki ad silindiğinde sınıftaki değer yine
  görünür hâle gelir.
- Sınıf gövdesi bir kez, örnek gövdesi her örnekte koşar: sınıfta tanımlanan bir kap **1** kez
  var edilip **3** eklemeyi birlikte tutar, örnekte tanımlanan **3** kez var edilir.
- `self` bir anahtar sözcük değildir: örnek üzerinden erişim her seferinde yeni bir **bağlı
  yöntem** üretir, o yöntem erişilen örneği taşır ve çağrıda ilk argüman olarak geçirir.
- Bağlamanın üç biçimi vardır; altı çağrının **beşi** karşılık bulur ve düşen tek çağrı örnek
  yönteminin örneksiz çağrılmasıdır. Gövdesi `Bicimci`'de yazılı bir sınıf yöntemi
  `AltBicimci` üzerinden çağrıldığında `cls`'e **`AltBicimci`** bağlanır.

#### Sonraki Adım

Bu derste yanıtı veren her zaman `Kayit`'ti, çünkü ortada tek bir sınıf vardı ve arama ilk
adımda bitiyordu. Sınıflar birbirinden türediğinde arama uzar: bir ad birden çok sınıfta yazılı
olabilir ve hangisinin bulunacağını **çözümleme sırası** belirler. Sonraki ders bu sırayı
ölçer ve kursun ikinci iddiasını öder — dört yeteneğin gövdesi bir kalıtım ağacında hangi
sınıflara dağılır, aynı dört yeteneği bileşimle veren bir sınıf kaçını kendi gövdesinde yazar
ve gövdesi bir sınıfta yazılı olan bir yöntem neden başka bir sınıfın adını basar?

### Kalıtım ve Yöntem Çözümleme Sırası

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/kalitim-ve-yontem-cozumleme-sirasi

Dört yeteneğin gövdesi kalıtımda üç ayrı sınıfa dağılır ve `Belge` bunların 0'ını kendi gövdesinde yazar, bileşimde `Sarmal` 4'ünü de yazar; gövdesi `Kayit`'te yazılı olan `bicimle` yöntemi `<Imzali>` basar.

Önceki ders tek sınıflı bir ağaçta yanıtın hep aynı yerden geldiğini gösterdi: arama örneğin
sözlüğüne bakıyor, bulamazsa sınıfa gidiyor ve orada bitiyordu. Son ölçüm sınırı çoktan
çizmişti — gövdesi `Bicimci`'de yazılı bir sınıf yöntemi `AltBicimci` üzerinden çağrıldığında
`cls`'e bağlanan sınıf `AltBicimci` oldu. Gövdenin durduğu yer ile çağrının yanıtlandığı yer
ayrıldı.

Bu ders o ayrımı büyütür ve kursun **ikinci iddiasını** öder. Aynı dört yetenek iki ayrı
tasarımla verilecek: biri kalıtım ağacı, öteki bileşim. İki tasarım da aynı sonuçları
üretecek. Sorulan şey sonuç değil: **her yeteneğin gövdesi hangi sınıfta yazılı?** Soru ölçüye
elverişlidir, çünkü yanıtı görüşe değil koda bakılarak verilir — bir sınıfın sözlüğünde bir ad
ya vardır ya yoktur.

#### Aynı Dört Yetenek, İki Tasarım

Kalıtım tarafı dört sınıftan oluşur. `Kayit` iki yetenek taşır: `kaynak` ve onu çağıran
`bicimle`. `Zamanli` ondan türer ve `damga` ekler. `Imzali` da ondan türer, `imza` ekler ve
`kaynak`'ı **yeniden yazar**. `Belge` ikisinden birden türer ve gövdesi boştur.

Bileşim tarafı tek sınıftır. `Sarmal` hiçbir şeyden türemez; iki nesneyi kendi içinde tutar
ve dört yeteneğin dördünü de kendi gövdesinde yazıp çağrıyı içerideki nesneye iletir.

Bir yeteneğin gövdesinin nerede yazılı olduğunu **yöntem çözümleme sırası (method resolution
order)** söyler. Her sınıfın böyle bir sırası vardır: aramanın hangi sınıfları hangi düzende
gezeceğini veren, tanım anında bir kez hesaplanmış bir liste. `saglayan` işlevi bu listeyi
gezer ve adı ilk taşıyan sınıfı döndürür.

`Belge`'nin gövdesinin boş olması bir eksiklik değil, ölçümün kendisidir. O gövde tek bir şey
bildirir: hangi sınıflardan, **hangi sırayla** türendiği. Sınıfın bütün davranışı bu tek
bildirimden doğar ve bildirim değiştiğinde davranış da değişir — tabanların yeri değiştirilse
`kaynak`'ı yanıtlayan sınıf değişir, `Belge`'nin gövdesine tek satır eklense yanıt oraya
kayar. Bileşimde bu bildirim yoktur; `Sarmal`'ın davranışı yalnız kendi gövdesinden okunur.

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

- **SN16** — Dört sınıflı ağaç ve `Sarmal` ortak kurgunun tanımladığı biçimdedir; yeteneklerin
  yerleşimi değiştirilmemiştir.
- **SN17** — Ölçülen dört yetenek `kaynak`, `bicimle`, `damga` ve `imza`'dır; ölçüm çağrının
  **sonucuna** değil, gövdenin **yazıldığı sınıfa** bakar.
- **SN18** — Bileşim sütunu sabit değildir, `Sarmal`'ın kendi sözlüğünden okunmuş olsaydı da
  aynı çıkardı: dört adın dördü de o gövdede yazılıdır.
- **SN19** — Çözümleme sırası basılırken listenin son öğesi (bütün sınıfların ortak tabanı)
  kırpılır; ölçülen şey kurgunun kendi dört sınıfıdır.

```python
"""Cagriyi kim yanitladi: kalitim ile bilesim yan yana."""


class Kayit:
    def kaynak(self):
        return "Kayit"

    def bicimle(self):
        return f"<{self.kaynak()}>"


class Zamanli(Kayit):
    def damga(self):
        return "zaman"


class Imzali(Kayit):
    def kaynak(self):
        return "Imzali"

    def imza(self):
        return "imza"


class Belge(Zamanli, Imzali):
    pass


class Sarmal:
    """Ayni yetenegi bilesimle veren nesne: yanit veren acikca yazilir."""

    def __init__(self):
        self.zamanli = Zamanli()
        self.imzali = Imzali()

    def kaynak(self):
        return self.imzali.kaynak()

    def damga(self):
        return self.zamanli.damga()

    def imza(self):
        return self.imzali.imza()

    def bicimle(self):
        return f"<{self.kaynak()}>"


def saglayan(sinif, ad):
    """Bir ozniteligi hangi sinif sagliyor: cozumleme sirasinda ilk bulan."""
    for s in sinif.__mro__:
        if ad in s.__dict__:
            return s.__name__
    return None


YETENEKLER = ("kaynak", "bicimle", "damga", "imza")

k = {ad: saglayan(Belge, ad) for ad in YETENEKLER}
b = {ad: "Sarmal" for ad in YETENEKLER}

print(f"{'yetenek':<10s} {'kalıtımda yanıtlayan':>21s} {'bileşimde yanıtlayan':>21s}")
for ad in YETENEKLER:
    print(f"{ad:<10s} {k[ad]:>21s} {b[ad]:>21s}")
print()
print("çözümleme sırası: " + " -> ".join(s.__name__ for s in Belge.__mro__[:-1]))
print(f"Belge().bicimle() -> {Belge().bicimle()} | Sarmal().bicimle() -> "
      f"{Sarmal().bicimle()}")
print(f"kalıtımda gövdesi Belge'de yazılı yetenek: "
      f"{sum(1 for a in YETENEKLER if k[a] == 'Belge')}/{len(YETENEKLER)}; "
      f"bileşimde: {sum(1 for a in YETENEKLER if b[a] == 'Sarmal')}/{len(YETENEKLER)}")
```

```
yetenek     kalıtımda yanıtlayan  bileşimde yanıtlayan
kaynak                    Imzali                Sarmal
bicimle                    Kayit                Sarmal
damga                    Zamanli                Sarmal
imza                      Imzali                Sarmal

çözümleme sırası: Belge -> Zamanli -> Imzali -> Kayit
Belge().bicimle() -> <Imzali> | Sarmal().bicimle() -> <Imzali>
kalıtımda gövdesi Belge'de yazılı yetenek: 0/4; bileşimde: 4/4
```

Son satır iddianın kendisidir. `Belge` dört yeteneğin **0**'ını kendi gövdesinde taşır;
`Sarmal` **4**'ünü de taşır. `Belge`'nin gövdesi okunduğunda görülen tek şey iki taban adıdır;
nesnenin ne yapabildiği o gövdede **yazılı değildir**. `Sarmal`'ın gövdesi okunduğunda ise
dört yetenek de, her birinin çağrıyı kime ilettiği de görünür.

Sol sütun yanıtın üç ayrı sınıfa dağıldığını gösteriyor: `kaynak` `Imzali`'den, `bicimle`
`Kayit`'ten, `damga` `Zamanli`'den geliyor. `kaynak` iki sınıfta birden yazılıdır — hem
`Kayit`, hem `Imzali` — ama çözümleme sırasında `Imzali` önce geldiği için yanıtı o veriyor.
`Kayit`'teki gövde hâlâ oradadır ve hiç çağrılmaz.

Asıl ince nokta ikinci satırdadır. `bicimle` gövdesi **`Kayit`'te** yazılıdır ve o gövdede
`self.kaynak()` yazar. Gövde `Kayit`'in içinde durduğu için oradaki `kaynak`'ı çağıracağı
düşünülebilir; **çağırmıyor.** Çağrı `self` üzerinden yapılır ve `self` bir `Belge`
örneğidir — arama `Belge`'nin sırasında yapılır ve `Imzali`'de durur. Sonuç `<Imzali>` çıkar.
**Bir gövdeyi okumak, o gövdenin ne üreteceğini söylemez;** onu, çağrı anındaki nesnenin
çözümleme sırası söyler. `Sarmal` aynı sonucu verir, ama orada iletimin nereye gittiği
gövdenin içinde yazılıdır.

Programlama Paradigmaları kursu bileşimin kalıtıma tercih edilmesini bir **tasarım ilkesi**
olarak, kırılgan üst sınıf sorunuyla birlikte kurmuştu; **bu tartışma tekrarlanmaz.** Burada
ölçülen şey ilkenin gerekçesi değil, ölçülebilir karşılığıdır: gövdenin **nerede yazılı**
olduğu. 0/4 ile 4/4 aynı davranışın iki ayrı okunabilirlik bedelidir.

İki sayının yönü terstir ve seçim tam olarak burada yapılır. Kalıtımda beşinci bir yetenek
eklemenin bedeli **0** satırdır: `Zamanli`'ye yazılan bir yöntem `Belge`'de kendiliğinden
görünür. Bileşimde bedel **1** satırdır — `Sarmal`'a bir iletim yazmak gerekir. Buna karşılık
"bu çağrıyı kim yanıtlıyor" sorusunun bedeli kalıtımda dört sınıf gövdesini ve aralarındaki
sırayı okumak, bileşimde tek gövdeyi okumaktır. Kalıtım yazımı kısaltır ve okumayı uzatır;
bileşim yazımı uzatır ve okumayı kısaltır.

Ayrımın bir de nesne tarafı vardır ve iki tanımdan doğrudan okunur. Bir `Belge` örneği **tek**
nesnedir; dört sınıfın gövdeleri tek bir sözlük üzerinde çalışır ve `Kayit`'in tutacağı her
durum o tek sözlükte, **bir kez** bulunur. Bir `Sarmal` örneği ise **üç** nesnedir — kendisi ve
`__init__` içinde var ettiği iki nesne — ve `Kayit`'ten gelen durum ikisinde **ayrı ayrı**
tutulur. İki tasarım aynı çağrılara aynı yanıtı verir ama durumu farklı sayıda yerde saklar:
kalıtımda paylaşım kaçınılmazdır, bileşimde paylaşım ancak açıkça yazılırsa olur.

#### Sıra Nasıl Kuruluyor

`Belge -> Zamanli -> Imzali -> Kayit` sırası keyfî değildir ve iki kuralla üretilir: her sınıf
kendi tabanlarından **önce** gelir ve tabanlar sınıf gövdesinde **yazıldıkları sırayla**
korunur. `Kayit` en sonda durur, çünkü hem `Zamanli`'nin hem `Imzali`'nin tabanıdır ve ikisi
de ondan önce gelmek zorundadır.

Bu iki kural her taban sıralaması için sağlanamaz. Sağlanamadığında sınıf **kurulamaz** ve
hata örnek var edilirken değil, `class` deyimi çalışırken alınır.

- **SN20** — Dört taban sıralaması aynı üç sınıftan kurulur; kurulan sınıfın kendisi çıktıdan
  düşürülür, yalnız ondan sonraki sıra basılır.
- **SN21** — Kurulamayan sıralamada ünlem önekiyle **istisnanın adı** kaydedilir.
- **SN22** — Sınıflar önceki bloğun tanımlarıdır; ağaç değiştirilmemiştir.

```python
def kurmayi_dene(tabanlar):
    """Verilen taban sirasiyla bir sinif kurulabiliyor mu?"""
    try:
        s = type("Deneme", tabanlar, {})
    except TypeError as e:
        return f"!{type(e).__name__}"
    return " -> ".join(t.__name__ for t in s.__mro__[1:-1])


print(f"{'taban sırası':<22s} {'kurulan sınıftan sonraki sıra':<34s}")
for tabanlar in ((Zamanli, Imzali), (Imzali, Zamanli),
                 (Zamanli, Kayit), (Kayit, Zamanli)):
    ad = "(" + ", ".join(t.__name__ for t in tabanlar) + ")"
    print(f"{ad:<22s} {kurmayi_dene(tabanlar):<34s}")

print()
print(f"Kayit'in Belge sırasındaki yeri: "
      f"{Belge.__mro__.index(Kayit)} (dört sınıfın sonuncusu)")
print(f"Zamanli'nin doğrudan tabanı: "
      f"{', '.join(t.__name__ for t in Zamanli.__bases__)}")
print(f"Belge sırasında Zamanli'den hemen sonra gelen: "
      f"{Belge.__mro__[Belge.__mro__.index(Zamanli) + 1].__name__}")
```

```
taban sırası           kurulan sınıftan sonraki sıra     
(Zamanli, Imzali)      Zamanli -> Imzali -> Kayit        
(Imzali, Zamanli)      Imzali -> Zamanli -> Kayit        
(Zamanli, Kayit)       Zamanli -> Kayit                  
(Kayit, Zamanli)       !TypeError                        

Kayit'in Belge sırasındaki yeri: 3 (dört sınıfın sonuncusu)
Zamanli'nin doğrudan tabanı: Kayit
Belge sırasında Zamanli'den hemen sonra gelen: Imzali
```

Dört sıralamanın **üçü** kuruluyor, **biri** düşüyor. İlk iki satır taban yazım sırasının
sonucu doğrudan belirlediğini gösteriyor: yerler değişince `kaynak`'ı yanıtlayan sınıf da
değişir. Dördüncü satır sınırı çiziyor — `Kayit` önce, türevi `Zamanli` sonra yazıldığında
"her sınıf tabanlarından önce gelir" kuralı sağlanamaz ve sınıf hiç kurulamaz. Bu bir çalışma
zamanı hatası değildir; **tanım anında** alınır. Sıra her sınıf için bir kez hesaplanır ve
sınıfın üzerinde saklanır; her öznitelik erişiminde yeniden kurulmaz. Aramanın bedeli bu
yüzden ağacın biçimine değil, adın sırada kaçıncı sınıfta bulunduğuna bağlıdır.

Son üç satır ilk tablodaki sıranın nereden geldiğini kapatıyor. `Kayit`, `Zamanli`'nin
**doğrudan** tabanıdır; buna rağmen `Belge` sırasında `Zamanli`'den hemen sonra gelen sınıf
`Kayit` değil, **`Imzali`**'dir. Sıra ağaçta yukarı doğru tek dal izlemez; bir sınıfı geçince
o dalın tabanına atlamaz, **ortak tabanı en sona bırakır.**

Bu kuralın gerekçesi ilk ölçümde duruyor. Sıra `Belge -> Zamanli -> Kayit -> Imzali` olsaydı
`kaynak` araması `Kayit`'te dururdu ve `Imzali`'nin yazdığı gövde hiç bulunamazdı — bir
kardeşin yeniden yazdığı yöntem, öteki kardeşin tabanı yüzünden kaybolurdu. Ortak taban en
sona alınarak bu kayıp önlenir: taban ancak **bütün** türevleri denendikten sonra sorulur.
Sıranın iki kuralı bu yüzden bir üslup tercihi değil, çoklu kalıtımın anlamlı olmasının
koşuludur.

#### `super()` Ataya Değil, Sıraya Gider

Bunun en görünür sonucu `super()` çağrısındadır. Adı bir "üst sınıf" çağrısı gibi okunur ama
yaptığı iş budur değildir: `super()`, **çağrıyı yapan nesnenin çözümleme sırasında**, gövdenin
yazıldığı sınıftan **hemen sonraki** sınıfa gider. Gövde aynı kalır, sıra değişir, gidilen yer
değişir.

- **SN23** — Ölçüm için dört yeni sınıf kurulur; ortak kurgunun ağacına dokunulmaz.
- **SN24** — Dört sınıfın dördü de aynı adı yazar ve gövdesi tek satırdır: kendi adını listeye
  koyup `super()` üzerinden zinciri sürdürür. Taban sınıf zinciri kapatır.
- **SN25** — Ölçülen şey çağrının sonucu değil, **koşan gövdelerin dizisidir**; sayı gövde
  sayısıdır.
- **SN26** — İkinci tablo `SolKatman`'ın gövdesini iki ayrı örnek üzerinden okur; gövde tek,
  örnekler iki ayrı sınıftandır.

```python
class Katman:
    def etiket(self):
        return ["Katman"]


class SolKatman(Katman):
    def etiket(self):
        return ["SolKatman"] + super().etiket()


class SagKatman(Katman):
    def etiket(self):
        return ["SagKatman"] + super().etiket()


class Birlesik(SolKatman, SagKatman):
    def etiket(self):
        return ["Birlesik"] + super().etiket()


def sonraki(sinif, govde):
    """Govdesi 'govde' sinifinda yazili super() cagrisi kime gider?"""
    m = sinif.__mro__
    return m[m.index(govde) + 1].__name__


print(f"{'örneğin sınıfı':<12s} {'koşan gövde':>11s}  zincir")
for s in (SolKatman, Birlesik):
    z = s().etiket()
    print(f"{s.__name__:<12s} {len(z):>11d}  {' -> '.join(z)}")

print()
print(f"{'gövdeyi yazan':<14s} {'örneğin sınıfı':<14s} super() kime gider")
for s in (SolKatman, Birlesik):
    print(f"{'SolKatman':<14s} {s.__name__:<14s} {sonraki(s, SolKatman)}")
```

```
örneğin sınıfı koşan gövde  zincir
SolKatman              2  SolKatman -> Katman
Birlesik               4  Birlesik -> SolKatman -> SagKatman -> Katman

gövdeyi yazan  örneğin sınıfı super() kime gider
SolKatman      SolKatman      Katman
SolKatman      Birlesik       SagKatman
```

`SolKatman` gövdesi iki ölçümde de **aynıdır** ve tek satırdır. Bir `SolKatman` örneğinde o
satırdaki `super()` çağrısı `Katman`'a gider ve zincir **2** gövdede biter. Bir `Birlesik`
örneğinde aynı satır **`SagKatman`**'a gider — `SolKatman`'ın tabanı olmayan, gövdesinde adı
bile geçmeyen bir sınıfa — ve zincir **4** gövdede biter.

`SagKatman`'ın hiç atlanmaması bu düzenin amacıdır: çoklu kalıtımda her sınıfın gövdesi
**tam bir kez** koşar ve ortak taban en sonda **bir kez** çalışır. Zincirin işlemesi için her
halkanın `super()` çağırması gerekir; bir sınıf çağırmayı bıraktığında ondan sonraki bütün
gövdeler sessizce düşer. Kurulum yöntemleri de aynı zincire tabidir: `__init__` içinde
`super().__init__(...)` yazılmadığında ara sınıfların kurulum gövdeleri hiç koşmaz ve eksiklik
ancak eksik alan okunduğunda ortaya çıkar.

Zincirin ikinci koşulu imzalardır. Bir gövde `super()` üzerinden kime gideceğini bilmediğine
göre, aynı adı taşıyan bütün gövdelerin **birbirini kabul eden** parametrelerle yazılmış olması
gerekir. `SolKatman` gövdesini yazan taraf `SagKatman`'ı hiç görmemiş olabilir; buna rağmen
ikisi aynı çağrıda yan yana koşar. Bileşimde böyle bir örtük sözleşme yoktur — `Sarmal.__init__`
içerideki iki nesneyi adlarıyla ve argümanlarıyla açıkça var eder, gidilecek yer sıraya değil
gövdeye yazılıdır.

Buradan tasarım tarafındaki sonuç çıkar. Bir gövdeyi doğru okumak için o gövdenin bulunduğu
sınıfı bilmek yetmez; **çağrının hangi nesneden geldiğini** de bilmek gerekir. Bileşimde böyle
bir bilinmez yoktur: `Sarmal.bicimle` gövdesindeki çağrı `self.imzali`'ye gider ve o ad
gövdenin içinde yazılıdır. Kalıtım kısa yazım verir ve yanıtlayanı gizler; bileşim uzun yazım
ister ve yanıtlayanı yazar.

Gizlenen şeyin okunamaz olmadığını da eklemek gerekir. Çözümleme sırası bir tahmin değil,
sınıfın üzerinde duran ve okunabilen bir listedir; `saglayan` işlevi o listeyi gezen üç
satırdır. Bir yeteneğin gövdesini arayan biri sınıf ağacını gözle izlemek zorunda değildir,
sırayı doğrudan sorabilir. Kalıtımın bedeli bilginin **yok olması** değil, çağrıyı okuyan
kişinin onu ayrıca sorması gerekmesidir.

#### Özet

- Bir yeteneği hangi sınıfın yanıtladığını **yöntem çözümleme sırası** belirler; ad birden çok
  sınıfta yazılıysa sırada ilk gelen kazanır ve ötekiler hiç çağrılmaz.
- `Belge` dört yeteneğin **0**'ını kendi gövdesinde taşır, yanıt üç ayrı sınıftan gelir;
  `Sarmal` aynı dört yeteneğin **4**'ünü de kendi gövdesinde yazar.
- `bicimle` gövdesi `Kayit`'te yazılıdır ama içindeki çağrı `self` üzerinden çözüldüğü için
  sonuç **`<Imzali>`** çıkar: gövdeyi okumak sonucunu söylemez.
- Çözümleme sırası iki kuralla kurulur — her sınıf tabanlarından önce, tabanlar yazıldıkları
  sırayla. Dört taban sıralamasının **üçü** kurulur, kural sağlanamayan **biri** tanım anında
  `TypeError` ile düşer.
- `super()` gövdenin yazıldığı sınıfın tabanına değil, **örneğin sırasında** o sınıftan sonra
  gelene gider: aynı `SolKatman` gövdesi bir örnekte `Katman`'a, ötekinde `SagKatman`'a gider
  ve zincir **2**'ye karşı **4** gövde koşar.

#### Sonraki Adım

Buraya kadar bütün adlar dışarıdan görünür kabul edildi: `Belge` örneğinin `kaynak`'ı da,
`Sarmal`'ın içinde tuttuğu `imzali` nesnesi de aynı biçimde okunabilir. Oysa `imzali`,
`Sarmal`'ın iç düzenidir; dışarıdan ona dokunan bir kod, `Sarmal`'ın yarın değiştirebileceği
bir ayrıntıya bağlanmış olur. İki tasarımın ayrıldığı yerlerden biri de budur: kalıtımda taban
sınıfın her adı türeve olduğu gibi geçer, bileşimde dışarıya ne verileceği gövdede tek tek
yazılır. Sonraki ders bu ayrımın Python'da nasıl bildirildiğini ölçer:
adın başına konan alt çizgi erişimi gerçekten **engelliyor** mu, iki alt çizgi ne değiştiriyor
ve engellemiyorsa bu bildirimin ölçülebilir bir işi var mı?

### Kapsülleme Sözleşmeleri

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/kapsulleme-sozlesmeleri

Dört erişim biçiminin 3'ü değeri verir ve adın engellediği erişim 0'dır; çift alt çizgi bir kilit değil bir ad değişimidir ve iki sınıfın aynı adı için 1 yerine 2 giriş açar.

Önceki ders iki tasarımı yanıtlayan sınıf üzerinden karşılaştırdı ve `Sarmal`'ın içeride iki
nesne tuttuğunu gösterdi. O iki nesne `Sarmal`'ın **iç düzenidir**: yarın biri kaldırılabilir,
adı değişebilir, ikisi tek nesnede birleşebilir. Ama ölçümde hiçbir şey onlara dışarıdan
dokunulmasını engellemedi — `nesne.imzali` yazımı `nesne.kaynak()` kadar geçerlidir.

Bu ders o sınırın nasıl bildirildiğini ölçer. Bazı diller görünürlüğü dilin kendisiyle
kapatır; Python kapatmaz. Sorulan şey şudur: adın başına konan alt çizgi erişimi gerçekten
**engelliyor** mu, iki alt çizgi ne değiştiriyor ve engellemiyorsa bu bildirimin ölçülebilir
bir işi var mı?

#### Üç Adlandırma, Dört Erişim

Bir sınıf üç ayrı adlandırma biçimi kullanabilir. Alt çizgisiz ad **dışarıya açıktır**: bu
adın adı, tipi ve anlamı sınıfın verdiği sözün parçasıdır. Tek alt çizgiyle başlayan ad
**içeridir**: sınıf onu kullanır, dışarıdan okuyan kendi sorumluluğuna okur ve sınıf onu
haber vermeden değiştirebilir. İki alt çizgiyle başlayan ad ise dilin **ad değişimi (name
mangling)** kuralını tetikler; adın önüne sınıfın adı eklenir.

Üçü de aynı biçimde atanır ve üçü de aynı sözlüğe yazılır. Ayrıldıkları yer, dışarıdan hangi
yazımla okunabildikleridir.

- **SN27** — Üç ad tek bir `__init__` gövdesinde, birbirinin aynısı olan üç atamayla kurulur;
  değerleri de adlarını yansıtır.
- **SN28** — Dört erişim biçimi denenir: üç ad doğrudan, çift alt çizgili ad ise bir de
  değişmiş yazımıyla. Erişemeyen biçim ünlem önekiyle **istisnanın adını** kaydeder.
- **SN29** — Sınıfın kendi gövdesinden yapılan okuma ayrıca basılır; ölçüm dışarıdan ve
  içeriden erişimi karşılaştırır.
- **SN30** — Örnek sözlüğündeki adlar sıralanmış olarak basılır; **hiçbir kimlik sayısı ya da
  ortama bağlı veri basılmaz.**

```python
"""Ad neyi engelliyor: uc gorunurluk bildirimi ve dort erisim bicimi."""


class Depo:
    """Uc ayri adlandirma bicimi; ucu de ayni sekilde atanir."""

    def __init__(self):
        self.acik = "acik"
        self._ic = "ic"
        self.__kapali = "kapali"

    def kendi_okur(self):
        return (self.acik, self._ic, self.__kapali)


d = Depo()

BICIMLER = (
    ("d.acik", lambda: d.acik),
    ("d._ic", lambda: d._ic),
    ("d.__kapali", lambda: getattr(d, "__kapali")),
    ("d._Depo__kapali", lambda: d._Depo__kapali),
)


def dene(cagri):
    try:
        return repr(cagri())
    except AttributeError as e:
        return f"!{type(e).__name__}"


print(f"{'yazım':<22s} {'dışarıdan erişim':>17s}")
for ad, cagri in BICIMLER:
    print(f"{ad:<22s} {dene(cagri):>17s}")

veren = sum(1 for _, c in BICIMLER if not dene(c).startswith("!"))
print()
print(f"sınıfın kendisi üçünü de okur -> {d.kendi_okur()}")
print(f"örnek sözlüğündeki adlar -> {sorted(d.__dict__)}")
print(f"dört erişim biçiminin {veren}'ü değeri verdi; "
      f"adın engellediği erişim {len(BICIMLER) - veren - 1}")
```

```
yazım                   dışarıdan erişim
d.acik                            'acik'
d._ic                               'ic'
d.__kapali               !AttributeError
d._Depo__kapali                 'kapali'

sınıfın kendisi üçünü de okur -> ('acik', 'ic', 'kapali')
örnek sözlüğündeki adlar -> ['_Depo__kapali', '_ic', 'acik']
dört erişim biçiminin 3'ü değeri verdi; adın engellediği erişim 0
```

Dört biçimin **üçü** değeri veriyor. İlk iki satır beklenen sonucu üretiyor: açık ad da, tek
alt çizgili ad da dışarıdan okunuyor. Tek alt çizginin erişime hiçbir etkisi yoktur; dilin
gözünde `_ic` ile `acik` arasında **hiçbir fark yoktur.** Aradaki tek fark, insanların ve
araçların okuduğu bir bildirimdir.

Üçüncü satır bir engel gibi görünüyor ama değildir; dördüncü satır bunu gösteriyor. Aynı değer
`d._Depo__kapali` yazımıyla sorunsuz okunuyor. Örnek sözlüğüne bakıldığında neden görünür: o
adın sözlükteki karşılığı zaten `_Depo__kapali`'dir. Çift alt çizgi bir kilit değil, **derleme
sırasında yapılan bir ad değişimidir** — sınıf gövdesindeki `self.__kapali` yazımı, adın önüne
sınıfın adı eklenmiş hâline çevrilir. `d.__kapali` yazımının başarısız olmasının nedeni erişimin
kapatılması değil, **öyle bir adın hiç var olmamasıdır.**

Bu yüzden son satırdaki sayı **0**'dır: dört biçimin hiçbirinde erişim engellenmedi, yalnız bir
biçimde aranan ad yanlıştı. Python'da görünürlük dilin uyguladığı bir kural değil, **sınıfın
verdiği bir bildirimdir**; uyup uymamak çağıranın kararıdır ve uymamanın bedeli çalışma zamanında
değil, sınıf değiştiğinde ödenir.

Bildirimin bir alıcısı olduğu için de boş bir jest değildir. Alt çizgili adlar sınıfın açık
yüzeyinin dışında sayılır: bir sınıfın hangi adlarını kullanmanın güvenli olduğunu soran bir
okuyucu, yardım metnini üreten bir araç ve `from ... import *` yazımının hangi adları
getireceğine karar veren kural aynı işareti okur. Bildirimin gücü dilin zorlamasından değil,
**herkesin aynı biçimde okumasından** gelir. Önceki dersin `Sarmal` sınıfı buna örnektir:
içeride tuttuğu iki nesnenin adı `_` ile yazılsaydı, dışarıdan onlara dokunan kodun kırılma
sorumluluğu açıkça devredilmiş olurdu.

#### Ad Değişimi Neyi Çözer

Çift alt çizgi erişim engellemiyorsa ne işe yarar? Ölçülebilir bir işi vardır ve tek başına
bir sınıfta görünmez: iki sınıf **aynı adı** kullandığında ortaya çıkar. Kalıtımda taban ve
türev tek bir örnek sözlüğünü paylaşır; ikisi de `self.durum` yazarsa ikinci yazma birincisini
**siler**. Ad değişimi her sınıfın adını öneke koyduğu için bu çakışmayı ortadan kaldırır.

- **SN31** — Taban ve türev aynı iki adı yazar; türevin kurulum gövdesi önce tabanınkini
  çağırır, sonra kendi değerlerini atar.
- **SN32** — Her sınıfın kendi gövdesinden yapılan okuma ayrı ayrı basılır; ölçülen şey aynı
  yazımın iki gövdede **hangi değeri** gördüğüdür.
- **SN33** — Son satır adların örnek sözlüğünde kaç giriş açtığını sayar.

```python
class Taban:
    def __init__(self):
        self._durum = "Taban"
        self.__durum = "Taban"

    def taban_okur(self):
        return {"tek": self._durum, "cift": self.__durum}


class Turev(Taban):
    def __init__(self):
        super().__init__()
        self._durum = "Turev"
        self.__durum = "Turev"

    def turev_okur(self):
        return {"tek": self._durum, "cift": self.__durum}


t = Turev()
print(f"{'yazım':<14s} {'Taban gövdesi okur':>19s} {'Turev gövdesi okur':>19s}")
for anahtar, ad in (("tek", "_durum"), ("cift", "__durum")):
    print(f"{ad:<14s} {t.taban_okur()[anahtar]:>19s} {t.turev_okur()[anahtar]:>19s}")

print()
print(f"örnek sözlüğündeki adlar -> {sorted(t.__dict__)}")
print(f"iki sınıfın tek alt çizgili adı için giriş "
      f"{sum(1 for a in t.__dict__ if a == '_durum')}, "
      f"çift alt çizgili adı için {sum(1 for a in t.__dict__ if a.endswith('__durum'))}")
```

```
yazım           Taban gövdesi okur  Turev gövdesi okur
_durum                       Turev               Turev
__durum                      Taban               Turev

örnek sözlüğündeki adlar -> ['_Taban__durum', '_Turev__durum', '_durum']
iki sınıfın tek alt çizgili adı için giriş 1, çift alt çizgili adı için 2
```

İlk satır çakışmanın kendisidir. `Taban`'ın gövdesi `self._durum` okuduğunda **`Turev`**
değerini görüyor — kendi yazdığı değeri değil. Türevin kurulumu tabanınkinden sonra koştuğu
için aynı sözlük girişinin üzerine yazdı ve tabanın değeri kayboldu. Taban sınıfı bu adı yazan
tek taraf olduğunu sanıyordu; bir alt sınıf farkında olmadan onu bozdu.

İkinci satırda çakışma yok. Her gövde kendi yazdığı değeri okuyor, çünkü iki yazım iki ayrı ada
çevrildi. Alt satırdaki sayılar bunu doğruluyor: tek alt çizgili ad iki sınıf için **1** giriş
açıyor, çift alt çizgili ad **2** giriş açıyor. Sözlükteki adlar da bunu gösteriyor.

Ad değişiminin çözdüğü sorun budur ve tek budur: bir sınıfın iç adının, kim türetirse türetsin
**yalnız kendisine** ait kalması. Bir sınıf başkalarının türetmesi beklenen bir tabansa ve iç
durumunun alt sınıflarca ezilmemesi gerekiyorsa çift alt çizgi ölçülebilir bir güvence verir.
Sıradan bir iç ad için ise tek alt çizgi yeterlidir — çift alt çizgi orada yalnız yazımı
uzatır ve alt sınıfın erişimini gereksizce zorlaştırır. Ad değişiminin **sınıf adına** bağlı
olduğunu da eklemek gerekir: iki ayrı yerde aynı ada sahip iki sınıf varsa önek de aynı çıkar
ve güvence düşer. Verilen söz "kimse erişemez" değil, "aynı adı taşımayan sınıflar
çakışmaz"dır.

#### Bildirim Değişmezi Korumaz

Kapsüllemenin amacı adı gizlemek değil, bir **değişmezi** ayakta tutmaktır. Nesneye dayalı
programlamada durumun korunması ve değişmezlerin sürdürülmesi Programlama Paradigmaları
kursunda kurulmuştu; **bu tartışma tekrarlanmaz.** Burada ölçülen şey bir soru: adlandırma
bildirimi değişmezi korumaya yeter mi?

- **SN34** — Ölçülen sınıfın tek değişmezi vardır: tutulan toplam, öğelerin toplamına eşit
  olmalıdır. Tutarlılık her yazmadan sonra sınanır.
- **SN35** — Beş yazma denenir: üçü sınıfın yöntemi üzerinden, ikisi doğrudan iç adlara.
  İkisi de tek alt çizgili adları kullanır ve **hiçbiri engellenmez.**
- **SN36** — "Gövdeden geçti" sütunu yazmanın sınıfın yöntemine uğrayıp uğramadığını, öteki
  sütun o yazmadan sonra değişmezin ayakta olup olmadığını gösterir.
- **SN37** — Yazmalar sırayla ve aynı nesne üzerinde yapılır; bozulan bir değişmez sonraki
  satırlarda da bozuk kalır.

```python
class Sayac:
    """Degismez: _toplam her zaman _ogeler toplamina esittir."""

    def __init__(self):
        self._ogeler = []
        self._toplam = 0

    def ekle(self, n):
        self._ogeler.append(n)
        self._toplam += n

    def tutarli(self):
        return self._toplam == sum(self._ogeler)


s = Sayac()
YAZMALAR = (
    ("s.ekle(2)", lambda: s.ekle(2)),
    ("s.ekle(3)", lambda: s.ekle(3)),
    ("s.ekle(5)", lambda: s.ekle(5)),
    ("s._toplam = 99", lambda: setattr(s, "_toplam", 99)),
    ("s._ogeler.append(7)", lambda: s._ogeler.append(7)),
)

print(f"{'yazma':<21s} {'gövdeden geçti':>15s} {'değişmez korundu':>17s}")
gecen = korunan = 0
for ad, islev in YAZMALAR:
    govdeden = ad.startswith("s.ekle")
    islev()
    korundu = s.tutarli()
    gecen += govdeden
    korunan += korundu
    print(f"{ad:<21s} {str(govdeden):>15s} {str(korundu):>17s}")

print()
print(f"yazma {len(YAZMALAR)}, gövdeden geçen {gecen}, "
      f"adın engellediği 0, değişmezin korunduğu {korunan}")
```

```
yazma                  gövdeden geçti  değişmez korundu
s.ekle(2)                        True              True
s.ekle(3)                        True              True
s.ekle(5)                        True              True
s._toplam = 99                  False             False
s._ogeler.append(7)             False             False

yazma 5, gövdeden geçen 3, adın engellediği 0, değişmezin korunduğu 3
```

Beş yazmanın **üçü** sınıfın gövdesinden geçti ve üçünde de değişmez ayakta kaldı. İkisi
geçmedi ve ikisi de değişmezi bozdu. Adın engellediği yazma sayısı yine **0**'dır: iki ihlal
de tek alt çizgili adlara yapıldı ve hiçbiri istisna üretmedi.

Son satır bir sınırı bozmayı da gösteriyor. `s._ogeler.append(7)` bir **atama** bile değil;
okunan bir nesne üzerinde yapılan bir değişikliktir. Bir adı okutmak, o adın gösterdiği
değiştirilebilir nesneyi de teslim etmektir. Kapsüllemenin bu tarafı adlandırmayla hiç
çözülemez; ancak dışarıya kopya vermekle ya da değiştirilemez bir kap vermekle çözülür ve
ikisi de gövdede alınmış bir karardır.

Ölçümün sıralı yapılması bir noktayı daha gösteriyor. Dördüncü satırda bozulan değişmez
beşinci satırda da bozuk; nesne kendini onarmıyor ve kimse durumu sorgulamıyor. Bozulmanın
ortaya çıkacağı yer, ihlalin yapıldığı satır değil, toplamın **okunduğu** herhangi bir satır
olacaktır. İhlal ile belirtinin arasının açılması, denetimsiz yazmanın asıl bedelidir.

"Kim" sorusunun bu dersteki yanıtı tektir: **değişmezi koruyan tek yer `ekle` gövdesidir.**
Ad ne yapıyorsa yapsın, o gövdeye uğramayan her yazma denetimsizdir. Adlandırma bildirimi
sözleşmenin **hangi yazımların desteklendiğini** söyler; sözleşmeyi **uygulayan** taraf değildir.
Uygulayan tarafı yazmak ayrı bir iştir ve erişimin kendisini araya bir gövde koyarak yakalamayı
gerektirir.

#### Gerçekten Engelleyen Tek Bildirim

Adlandırma hiçbir şeyi engellemiyorsa, engelleyen bir bildirim var mıdır? Bir tane vardır ve
görünürlüğü değil, **öznitelik kümesini** bildirir. Bir sınıf gövdesinde `__slots__` adıyla
yazılan bir dizi, o sınıfın örneklerinin hangi adları taşıyabileceğini sabitler; bildirilmemiş
bir ada yazma çalışma zamanında düşer ve örnekler ayrı bir sözlük taşımaz.

- **SN38** — İki sınıf aynı kurulum gövdesini paylaşır; tek fark birinin öznitelik kümesini
  bildirmesi, ötekinin bildirmemesidir.
- **SN39** — İki yazma denenir: bildirilmiş bir ada ve bir harf farkla yanlış yazılmış ada.
  İkincisi bilerek yanlıştır.
- **SN40** — "Örnek sözlüğü" sütunu örneğin ayrı bir öznitelik sözlüğü taşıyıp taşımadığını
  bildirir; **içeriği ve büyüklüğü basılmaz.**
- **SN41** — "Engellenen" sütunu iki yazmadan kaçının istisnayla düştüğünü sayar.

```python
class Serbest:
    """Oznitelik kumesini bildirmez: her ad yazilabilir."""

    def __init__(self, ad):
        self.ad = ad


class Kisitli:
    """Oznitelik kumesini bildirir: yalniz bildirilen adlar yazilabilir."""

    __slots__ = ("ad",)

    def __init__(self, ad):
        self.ad = ad


def yaz(nesne, alan):
    try:
        setattr(nesne, alan, "yeni")
    except AttributeError as e:
        return f"!{type(e).__name__}"
    return "geçti"


print(f"{'sınıf':<10s} {'ad yazımı':>16s} {'adi yazımı':>16s} "
      f"{'örnek sözlüğü':>14s} {'engellenen':>11s}")
for s in (Serbest, Kisitli):
    n = s("k")
    sonuc = [yaz(n, "ad"), yaz(n, "adi")]
    print(f"{s.__name__:<10s} {sonuc[0]:>16s} {sonuc[1]:>16s} "
          f"{('var' if hasattr(n, '__dict__') else 'yok'):>14s} "
          f"{sum(1 for c in sonuc if c.startswith('!')):>11d}")
```

```
sınıf             ad yazımı       adi yazımı  örnek sözlüğü  engellenen
Serbest               geçti            geçti            var           0
Kisitli               geçti  !AttributeError            yok           1
```

İlk satır bildirimsiz durumun sessizliğini gösteriyor: yanlış yazılmış ad da **geçiyor** ve
nesneye yeni bir öznitelik ekliyor. Program hata vermez; yalnız `ad` eski değerinde kalır ve
kimse bunu fark etmez. Alt satırda aynı yazma **düşüyor**; engellenen yazma sayısı **1**'e
çıkıyor.

Bu, dersin tek gerçek engelidir ve dikkat çekici biçimde bir **görünürlük** engeli değildir.
`__slots__` bildirimi hangi adların yazılabileceğini söyler; hangi adların dışarıdan
okunabileceğini yine söylemez. Bildirilmiş bir ad, alt çizgili de olsa dışarıdan okunmaya
açıktır. Yani Python'da uygulanan tek kapsülleme kuralı, kümenin **kapalılığıdır**, kümenin
**gizliliği** değildir.

Örnek sözlüğünün yokluğu bunun düzeneğini de açıklıyor. Bildirim yapıldığında örnek adları
serbest bir sözlükte tutmaz; her ad için sınıf üzerinde sabit bir yuva kurulur ve o yuvalar
dışında yazılacak yer kalmaz. Engelin kaynağı bir yasak değil, **yerin yokluğudur.**

#### Özet

- Python'da görünürlük dilin uyguladığı bir kural değil, adlandırmayla yapılan bir bildirimdir;
  dört erişim biçiminin **3**'ü değeri verir ve adın engellediği erişim **0**'dır.
- Tek alt çizgili adın erişime hiçbir etkisi yoktur; sınıfın iç düzenine ait olduğunu ve haber
  verilmeden değişebileceğini bildirir.
- Çift alt çizgi bir kilit değil, sınıf adını öneke koyan bir **ad değişimidir**; `d.__kapali`
  yazımının düşmesinin nedeni erişimin kapatılması değil, o adın hiç var olmamasıdır.
- Ad değişiminin ölçülebilir işi çakışmayı önlemektir: taban ile türevin aynı adı için tek alt
  çizgide **1** giriş açılır ve tabanın değeri kaybolur, çift alt çizgide **2** giriş açılır ve
  her gövde kendi değerini okur.
- Değişmezi koruyan tek yer yöntem gövdesidir; beş yazmanın **3**'ü gövdeden geçti ve değişmez
  **3**'ünde ayakta kaldı, geçmeyen **2** yazma onu bozdu.
- Uygulanan tek bildirim öznitelik kümesini kapatan `__slots__`'tur: bildirimsiz sınıfta yanlış
  yazılmış ad sessizce **geçer**, bildirimli sınıfta **düşer**. Kapatılan şey kümedir,
  görünürlük değildir.

#### Sonraki Adım

Buraya kadar yazdığımız sınıflar kendi yöntemleriyle çağrıldı: `ekle`, `bicimle`, `kaynak`.
Oysa önceki iki kursta ölçülen protokollerin çoğu yöntem adıyla değil, **sözdizimle**
çağrılıyordu — `len(n)`, `n == m`, `with n`, `for x in n`. O sözdizimleri dilin verdiği
nesneler karşılıyordu ve karşılayan taraf hep hazırdı. Sonraki ders karşılayan tarafı bizim
yazdığımız durumu ölçer: kendi sınıfımız hiçbir özel yöntem yazmadan bu sözdizimlerin kaçına
katılabiliyor, katılamadıklarında ne oluyor ve yazdığımız her yöntem hangi sözdizimi açıyor?

### Özel Yöntemler

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/ozel-yontemler

Hiçbir özel yöntem yazmayan sınıf on bir sözdizim biçiminin 5'ine katılır ama hiçbirini kendisi yanıtlamaz; `__eq__` yazmak `__hash__` adını boşaltır ve tek yöntem iki sözdizimi birden kapatır.

Önceki üç ders sınıfları kendi yazdığımız yöntemlerle çağırdı: `ekle`, `bicimle`, `kaynak`.
Oysa Python Temelleri kursu sözdizimin bir kısaltma olduğunu ölçmüştü — `n + m` bir yöntem
çağrısıdır, `x in n` bir yöntem çağrısıdır, `for` bir protokoldür. **O listeler
tekrarlanmaz.** Orada ölçülen taraf **çağıran** taraftı ve karşılayan taraf hep hazırdı:
liste, dizgi, sözlük, üreteç. Katılan nesneleri dil vermişti.

Bu ders karşılayan tarafı bizim yazdığımız durumu ölçer. İki soru vardır. Birincisi: hiçbir
özel yöntem yazmayan bir sınıf bu sözdizimlerin kaçına katılabilir ve katıldıklarında **yanıtı
kim veriyor**? İkincisi: yazdığımız her yöntem bir sözdizim açar da, hiç kapatır mı?

#### Yazmayan Sınıf Ne Yapabiliyor

On bir sözdizim biçimi seçilir; her biri belli bir özel yöntem adını arar. İki sınıf aynı
veriyi taşır. `Ciplak` hiçbir özel yöntem yazmaz. `Yazili` on bir yöntemin on birini de yazar.
Ölçüm iki şeyi ayrı ayrı sorar: sözdizim **çalıştı mı** ve o adı **hangi sınıf sağlıyor**.
İki sorunun ayrı sorulması gerekir, çünkü yanıtları her satırda aynı çıkmaz — bir sözdizim
çalışmadan da adı bulunmuş olabilir, bulunmadan da çalışmış olabilir.

- **SN42** — İki sınıf aynı üç öğeyi taşır ve `__init__` gövdeleri birbirinin aynısıdır;
  aralarındaki tek fark yazılan özel yöntemlerdir. `Yazili` `Ciplak`'tan türemez.
- **SN43** — Her biçim iki taze örnek üzerinde çalıştırılır; ölçüm sonucun değerine değil,
  çağrının karşılanıp karşılanmadığına bakar. Karşılanmayan çağrı `TypeError` ile düşer.
- **SN44** — "Yanıtlayan" sütunu adı çözümleme sırasında ilk taşıyan sınıfı verir; hiçbir sınıf
  taşımıyorsa `None` yazılır.
- **SN45** — Hiçbir sözdizimin **sonucu basılmaz**. `Ciplak` bir temsil yöntemi yazmadığı için
  varsayılan temsili **ortama bağlı veri taşır**; ölçüm bu yüzden yalnız durum ve yanıtlayan
  sınıf basar.

```python
"""Sozdizime katilan tarafi biz yaziyoruz: yazmayan ve yazan iki sinif."""


def saglayan(sinif, ad):
    for s in sinif.__mro__:
        if ad in s.__dict__:
            return s.__name__
    return None


class Ciplak:
    """Hicbir ozel yontem yazmaz; yalnizca ogelerini tasir."""

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


class Yazili:
    """Ayni veriyi tasir, katildigi her sozdizim icin bir yontem yazar."""

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

    def __repr__(self):
        return f"Yazili({self.ogeler})"

    def __str__(self):
        return "-".join(str(x) for x in self.ogeler)

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

    def __eq__(self, o):
        return isinstance(o, Yazili) and self.ogeler == o.ogeler

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

    def __bool__(self):
        return bool(self.ogeler)

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

    def __iter__(self):
        return iter(self.ogeler)

    def __enter__(self):
        return self

    def __exit__(self, *k):
        return False

    def __getitem__(self, i):
        return self.ogeler[i]

    def __hash__(self):
        return hash(tuple(self.ogeler))


def girdi(n):
    with n:
        return True


BICIMLER = (
    ("repr(n)", "__repr__", lambda n, m: repr(n)),
    ("str(n)", "__str__", lambda n, m: str(n)),
    ("len(n)", "__len__", lambda n, m: len(n)),
    ("n == m", "__eq__", lambda n, m: n == m),
    ("n < m", "__lt__", lambda n, m: n < m),
    ("bool(n)", "__bool__", lambda n, m: bool(n)),
    ("2 in n", "__contains__", lambda n, m: 2 in n),
    ("for x in n", "__iter__", lambda n, m: [x for x in n]),
    ("with n", "__enter__", lambda n, m: girdi(n)),
    ("n[0]", "__getitem__", lambda n, m: n[0]),
    ("hash(n)", "__hash__", lambda n, m: hash(n)),
)


def olc(sinif, islev):
    try:
        islev(sinif(), sinif())
    except TypeError:
        return "düştü"
    return "çalıştı"


print(f"{'sözdizim':<12s} {'özel yöntem':<14s} {'Ciplak':>8s} "
      f"{'yanıtlayan':>11s} {'Yazili':>8s} {'yanıtlayan':>11s}")
for ad, yontem, islev in BICIMLER:
    print(f"{ad:<12s} {yontem:<14s} {olc(Ciplak, islev):>8s} "
          f"{str(saglayan(Ciplak, yontem)):>11s} {olc(Yazili, islev):>8s} "
          f"{str(saglayan(Yazili, yontem)):>11s}")

for sinif in (Ciplak, Yazili):
    calisan = sum(1 for _, _, f in BICIMLER if olc(sinif, f) == "çalıştı")
    kendi = sum(1 for _, y, _ in BICIMLER if saglayan(sinif, y) == sinif.__name__)
    print(f"\n{sinif.__name__}: çalışan {calisan}/{len(BICIMLER)}, "
          f"yanıtı kendi veren {kendi}/{len(BICIMLER)}")
```

```
sözdizim     özel yöntem      Ciplak  yanıtlayan   Yazili  yanıtlayan
repr(n)      __repr__        çalıştı      object  çalıştı      Yazili
str(n)       __str__         çalıştı      object  çalıştı      Yazili
len(n)       __len__           düştü        None  çalıştı      Yazili
n == m       __eq__          çalıştı      object  çalıştı      Yazili
n < m        __lt__            düştü      object  çalıştı      Yazili
bool(n)      __bool__        çalıştı        None  çalıştı      Yazili
2 in n       __contains__      düştü        None  çalıştı      Yazili
for x in n   __iter__          düştü        None  çalıştı      Yazili
with n       __enter__         düştü        None  çalıştı      Yazili
n[0]         __getitem__       düştü        None  çalıştı      Yazili
hash(n)      __hash__        çalıştı      object  çalıştı      Yazili

Ciplak: çalışan 5/11, yanıtı kendi veren 0/11

Yazili: çalışan 11/11, yanıtı kendi veren 11/11
```

`Ciplak` on bir biçimin **5**'ine katılıyor ve yanıtı kendi veren biçim sayısı **0**. Yazmamak
sözdizimi kapatmaz; **sessiz bir varsayılana bırakır.** Çalışan beş satırın dördünde yanıtı
bütün sınıfların ortak tabanı veriyor, `bool(n)` satırında ise hiçbir sınıf vermiyor — orada
sözdizim bir yöntem bulamadığında düşmek yerine "her nesne doğrudur" kuralına geçiyor.

Varsayılanların ne söylediği önemlidir, çünkü hepsi **kimliğe** dayanır. Ortak tabanın verdiği
eşitlik iki ayrı nesneyi eşit saymaz; verdiği karma değeri de kimlikten üretilir. `Ciplak`
öğeleriyle karşılaştırılmak ya da öğeleriyle anahtar olmak isterse bu varsayılanlar yanlış
yanıt verir — hata vermezler, **yanlış yanıt** verirler. Bir hatanın en pahalı biçimi budur:
aynı içeriği taşıyan iki `Ciplak` nesnesi bir kümeye konduğunda iki ayrı öğe sayılır ve program
hiçbir uyarı vermeden çalışmaya devam eder.

`n < m` satırı ayrı bir okumadır. Yanıtlayan sütunu boş değil, ortak tabanı gösteriyor; ad
bulundu. Buna rağmen sözdizim **düştü**. Ortak taban bu adı tanımlar ama gövdesi "bu işi
bilmiyorum" der ve karşılaştırma karşılıksız kalır. **Adın bulunması yanıtın verildiği anlamına
gelmez.** Bir yeteneği aramanın doğru yolu adın varlığına bakmak değil, çağrının karşılanıp
karşılanmadığına bakmaktır.

`Yazili` on bir biçimin on birine katılıyor ve on birinin de yanıtını kendisi veriyor. Bir
sınıfın "dilin nesnelerine benzemesi" ayrı bir yetenek değildir; **yazılan yöntemlerdir.**
Katılım tipin adından ya da tabanından değil, sınıfın sözlüğüne konan adlardan gelir.

Sayının bir ayrıntısı tabloda görünmüyor. `Yazili` on bir sözdizim için **on iki** gövde
yazar: bağlam sözdizimi tek satırdır ama girişi ve çıkışı ayrı iki yöntemle karşılanır ve
tablo yalnız girişi listeler. Sözdizim ile yöntem arasındaki eşleme birebir değildir; bir
sözdizim birden çok gövde isteyebilir, `bool(n)` satırında görüldüğü gibi hiç gövde
istemeyebilir de.

Yazılan yöntemlerin bazıları kendilerine bitişik sözdizimleri de açar. Eşitliği yazan sınıf
eşitsizliği ayrıca yazmaz; küçüklüğü yazan sınıf, aynı tipten iki nesne arasında büyüklük
karşılaştırmasını da karşılar, çünkü dil işlemi ters çevirip öteki işlenene sorar. Bunlar
Python Temelleri kursunda **çağıran** taraftan ölçülmüştü; burada aynı düşüşlerin **yazan**
taraftaki karşılığı görülüyor — bir gövde yazmak, o gövdeye düşen bütün yazımları birden
karşılamaktır.

#### Bir Yöntem Yazmak Bir Sözdizimi Kapatabilir

Yazılan yöntemler yalnız kapı açmaz. Eşitlikle karma değeri arasında dilin sürdürdüğü bir
sözleşme vardır: eşit sayılan iki nesnenin karma değeri de eşit olmalıdır. Bir sınıf eşitliği
kendisi tanımladığında bu sözleşme kendiliğinden sağlanamaz hâle gelir ve dil kararı sınıfa
bırakmaz — karma yeteneğini **kaldırır**.

- **SN46** — Üç sınıf aynı tek alanı taşır; farkları yalnız hangi yöntemi yazdıklarıdır.
  Üçüncü sınıf ikincisinden türer ve yalnız karma yöntemini ekler.
- **SN47** — Karma sınaması değerin kendisini basmaz, yalnız çağrının karşılanıp
  karşılanmadığını bildirir; **karma değeri ortama bağlı olabilir.**
- **SN48** — Son iki sütun `__hash__` adının sınıfın kendi sözlüğünde bulunup bulunmadığını ve
  değerinin boş olup olmadığını ayrı ayrı gösterir.

```python
class Yalin:
    """Ne esitlik ne karma yazar."""

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


class Esit:
    """Yalniz esitligi yazar."""

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

    def __eq__(self, o):
        return isinstance(o, Esit) and self.deger == o.deger


class EsitVeKarma(Esit):
    """Esitligi devralir, karmayi kendisi yazar."""

    def __hash__(self):
        return hash(self.deger)


def dene(islev):
    try:
        return str(islev())
    except TypeError as e:
        return f"!{type(e).__name__}"


print(f"{'sınıf':<13s} {'a == b':>8s} {'hash(a)':>16s} {'anahtar olma':>16s} "
      f"{'__hash__ yazılı':>16s} {'değeri boş':>11s}")
for sinif in (Yalin, Esit, EsitVeKarma):
    a, b = sinif(), sinif()
    print(f"{sinif.__name__:<13s} {dene(lambda: a == b):>8s} "
          f"{dene(lambda: hash(a) is not None):>16s} "
          f"{dene(lambda: len({a: 1})):>16s} "
          f"{str('__hash__' in sinif.__dict__):>16s} "
          f"{str(sinif.__hash__ is None):>11s}")
```

```
sınıf           a == b          hash(a)     anahtar olma  __hash__ yazılı  değeri boş
Yalin            False             True                1            False       False
Esit              True       !TypeError       !TypeError             True        True
EsitVeKarma       True             True                1             True       False
```

İlk satır varsayılan durumu gösteriyor: iki ayrı nesne eşit değil, ama karma değeri var ve
nesne bir kapta anahtar olabiliyor. İkinci satırda tek bir yöntem yazıldı ve **iki** sözdizim
birden kapandı — hem `hash(a)` hem anahtar olma düştü. Yazılan yöntem `__eq__`'dı; kapanan
sözdizimlerin ikisi de `__hash__`'e bağlıydı.

Son iki sütun bunun nasıl olduğunu söylüyor. `Esit` sınıfının kendi sözlüğüne `__hash__` adı
**yazılmıştır** ve değeri **boştur**. Dil, eşitliği yazan sınıfa sessizce bir giriş ekler ve o
girişi boş bırakır; arama artık ortak tabana ulaşamaz, ilk adımda boş bir değere çarpar. Bu bir
yan etki değil, bilinçli bir karardır: eşitliğini değiştirmiş bir nesnenin eski karma değerini
kullanması, onu bir kapta **kaybolmuş** bir anahtar hâline getirirdi.

Üçüncü satır çözümü gösteriyor. `EsitVeKarma` eşitliği devralır ve karmayı kendisi yazar; giriş
artık boş değildir ve iki sözdizim de geri açılır. Kural şudur: **eşitliği yazan sınıf karmayı
da yazar** — ya da yazmayarak nesnenin anahtar olamayacağını kabul eder. Karma yazılırken
eşitlikte kullanılan aynı alanların kullanılması gerekir, yoksa sözleşme yine bozulur.

Sözleşmenin ikinci yarısı da yazan tarafın sorumluluğundadır: karma değeri nesnenin **ömrü
boyunca** değişmemelidir. Eşitliği değiştirilebilir bir alandan üreten bir sınıf, o alanı bir
kapta anahtarken değiştirdiğinde nesneyi kendi kabında bulunamaz hâle getirir. Bu yüzden
karmalanabilir olması istenen sınıfların eşitliği **değiştirilmeyen** alanlardan üretmesi
beklenir. `Yazili`'nin ilk ölçümdeki karma gövdesinin öğeleri bir demete çevirmesi de bu
nedenledir: değiştirilebilir bir kabın kendisi karmalanamaz.

#### İki Temsil

Kalan ayrım iki temsil yöntemi arasındadır. İkisi de nesneden bir dizgi üretir ama hedefleri
ayrıdır: biri **çözümleyici** için nesneyi tanıtır, öteki **okuyucu** için nesneyi sunar.
Hangisinin çağrıldığını kullanım yeri belirler.

- **SN49** — İki sınıf aynı iki dizgiyi döndürür; farkları yalnız birinin metin yöntemini
  yazmamasıdır.
- **SN50** — Altı kullanım biçimi denenir ve her biri taze bir örnek üzerinde çalışır; ölçüm
  dönen dizginin **hangisi** olduğuna bakar.
- **SN51** — Son satırlar altı biçimden kaçının metin yöntemine gittiğini sayar; kap içindeki
  basım da bu sayıya dahildir.

```python
class YalnizTemsil:
    """Yalniz __repr__ yazar."""

    def __repr__(self):
        return "temsil"


class IkiTemsil:
    """Ikisini de yazar."""

    def __repr__(self):
        return "temsil"

    def __str__(self):
        return "metin"


KULLANIM = (
    ("str(n)", lambda n: str(n)),
    ("repr(n)", lambda n: repr(n)),
    ("f-dizgide n", lambda n: f"{n}"),
    ("f-dizgide n!r", lambda n: f"{n!r}"),
    ("[n] basımı", lambda n: str([n])),
    ("biçimlendirme", lambda n: "{}".format(n)),
)

print(f"{'yazım':<15s} {'yalnız __repr__ yazan':>22s} {'ikisini de yazan':>18s}")
for ad, islev in KULLANIM:
    print(f"{ad:<15s} {islev(YalnizTemsil()):>22s} {islev(IkiTemsil()):>18s}")

for sinif in (YalnizTemsil, IkiTemsil):
    metin = sum(1 for _, f in KULLANIM if "metin" in f(sinif()))
    print(f"\n{sinif.__name__}: __str__ sonucunu veren yazım {metin}/{len(KULLANIM)}")
```

```
yazım            yalnız __repr__ yazan   ikisini de yazan
str(n)                          temsil              metin
repr(n)                         temsil             temsil
f-dizgide n                     temsil              metin
f-dizgide n!r                   temsil             temsil
[n] basımı                    [temsil]           [temsil]
biçimlendirme                   temsil              metin

YalnizTemsil: __str__ sonucunu veren yazım 0/6

IkiTemsil: __str__ sonucunu veren yazım 3/6
```

Sol sütunda altı yazımın altısı da aynı sonucu veriyor ve metin yöntemine giden yazım **0**.
Metin yöntemi yazılmadığında istek karşılıksız kalmaz, **temsil yöntemine düşer**. Bu düşüşün
yönü tek taraflıdır: temsil yöntemi yazılmadığında metin yöntemine düşüş **yoktur**. İki
yöntemden yalnız biri yazılacaksa yazılacak olan **temsildir**.

Sağ sütunda altı yazımın **3**'ü metin yöntemine gidiyor. Ayrım açık: dizgiye çevirme ve
biçimlendirme okuyucuya sunar, açık istek ve dönüştürme işareti çözümleyiciye tanıtır. Beşinci
satır en çok şaşırtan yerdir — nesne bir kabın içindeyken **her zaman** temsil yöntemiyle
basılır, kap dışarıdan metne çevrilmiş olsa bile. Kap kendi metnini üretirken içindeki her
öğeden temsil ister, çünkü çözümleyicinin göreceği şey öğenin sunumu değil, kimliğidir.

Buradan yazım kuralı çıkar: temsil yöntemi nesneyi **yeniden kurabilecek** kadar bilgi
taşımalıdır; metin yöntemi ise okunabilir olmalıdır. `Yazili` sınıfının ilk ölçümdeki iki
gövdesi tam olarak bu ayrımı izler: temsil gövdesi sınıfın adını ve öğelerini birlikte verir,
metin gövdesi yalnız öğeleri okunabilir biçimde dizer.

Bu ayrımın hata ayıklamadaki karşılığı doğrudan ölçülebilir bir kolaylıktır. Bir kapta duran
yüz nesnenin dökümü alındığında görülecek olan şey, o sınıfın temsil gövdesidir; temsil
yazılmamışsa döküm yüz satır boyunca hiçbir şey söylemez. Yazılan tek bir gövde, nesnenin
göründüğü **bütün** kaplarda okunabilirlik kazandırır.

#### Özet

- Hiçbir özel yöntem yazmayan sınıf on bir sözdizimin **5**'ine katılır ve **0**'ının yanıtını
  kendisi verir; yazmamak sözdizimi kapatmaz, sessiz bir varsayılana bırakır ve varsayılanların
  hepsi kimliğe dayanır.
- Ad bulunmuş olması yanıtın verildiği anlamına gelmez: `n < m` için ortak tabanda bir ad
  vardır ama sözdizim yine de düşer.
- On bir yöntemi yazan sınıf on bir sözdizimin tamamına katılır ve tamamının yanıtını kendisi
  verir; katılım tipin adından değil, yazılan yöntemlerden gelir.
- Bir yöntem yazmak bir sözdizimi kapatabilir: `__eq__` yazan sınıfın sözlüğüne `__hash__` adı
  **boş** olarak eklenir ve karma ile anahtar olma birlikte düşer. Eşitliği yazan sınıf karmayı
  da yazar.
- Metin yöntemi yazılmadığında istek temsil yöntemine düşer, tersi olmaz; ikisini de yazan
  sınıfta altı yazımın **3**'ü metne gider ve kap içindeki basım her zaman temsili kullanır.

#### Sonraki Adım

Bu derste yazılan her yöntem bir **çağrıyla** tetiklendi: `len(n)` yazıldığında bir gövde
koştu, `n[0]` yazıldığında bir gövde koştu. Öznitelik okumak ise hiçbir gövdeyi
çalıştırmıyordu — `n.ogeler` bir sözlük aramasıydı ve araya kimse girmiyordu. Üçüncü dersin
bıraktığı açık tam buradaydı: değişmezi koruyan gövdeye uğramayan yazma denetimsiz kalıyordu.
Sonraki ders erişimin kendisini araya bir gövde koyarak yakalamayı ölçer: aynı sayıda okuma ve
yazma, doğrudan alanla kaç gövde çalıştırır, araya giren bir tanımla kaç gövde çalıştırır ve
araya giren kod verisini nerede tutarsa üç örnek tek değeri paylaşmaya başlar?

### Özellikler ve Betimleyiciler

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/ozellikler-ve-betimleyiciler

Aynı yedi erişim doğrudan alanda 0 gövde çalıştırır ve geçersiz değeri geçirir, araya giren tanımda 7 gövde çalıştırır ve düşürür; betimleyici veriyi kendi üzerinde tutarsa üç örnek tek değeri paylaşır.

Önceki ders sözdizime katılan tarafı yazdı: `len(n)` bir gövde çalıştırdı, `n[0]` bir gövde
çalıştırdı. Öznitelik okumak ise hiçbir gövde çalıştırmadı — `n.ogeler` bir sözlük aramasıydı
ve araya kimse girmedi. Üçüncü dersin bıraktığı açık tam buradaydı: değişmezi koruyan gövdeye
uğramayan yazma denetimsiz kalıyordu ve adlandırma bunu engellemiyordu.

Bu ders erişimin kendisini yakalamayı ölçer. Ölçünün ekseni değişmiyor: sorulan şey yine
çağrıyı kimin yanıtladığıdır, ama bu kez çağrı bir yöntem çağrısı gibi görünmüyor — bir alan
okuması gibi görünüyor. Bir öznitelik okuması ve yazması da bir gövdeye
uğratılabilir; uğratıldığında yazımı değişmez, bedeli değişir. Üç soru sorulur: aynı erişim
dizisi iki tasarımda **kaç gövde** çalıştırır, araya giren kod **verisini nerede tutar** ve
erişimi yakalayan iki kancadan hangisi **kaç kez** çalışır?

#### Erişim Kaç Gövdeye Uğradı

Üç sınıf aynı tek alanı sunar ve dışarıdan üçü de `n.deger` yazımıyla kullanılır. `Dogrudan`
alanı çıplak tutar. `Ozellikli` alanı bir **özellik (property)** olarak tanımlar: okuma ve
yazma için birer gövde yazar. `Betimleyicili` aynı denetimi bir **betimleyiciye (descriptor)**
devreder — erişim mantığını kendi sınıfında taşıyan, sınıf gövdesinde bir kez var edilen bir
nesneye.

Üç sınıfa aynı iş verilir: değeri üç kez bir artırmak ve sonra geçersiz bir değer yazmayı
denemek.

- **SN52** — Üç sınıfın alanı da `deger` adıyla sunulur; çağıran taraf üç sınıfta da aynı
  satırları yazar.
- **SN53** — Ölçülen iş yedi erişimdir: üç artırma her turda **bir okuma ve bir yazma** üretir,
  sonuncu satır bir yazma daha ekler. Kurulumda oluşan kayıtlar sayıma girmez.
- **SN54** — Geçersiz değer bilerek negatiftir; denetim yazan gövdenin içindedir ve
  `ValueError` fırlatır. Denetim yoksa değer olduğu gibi yazılır.
- **SN55** — "Son değer" sütunu ölçüm bittikten sonra alandan okunan değerdir; bu okuma
  sayıma dahil değildir.

```python
"""Erisim kac kez araya giren koda ugradi: dogrudan alan, ozellik, betimleyici."""

KAYIT = []


class Dogrudan:
    """Alan ciplaktir; erisim hicbir govdeye ugramaz."""

    def __init__(self):
        self.deger = 0


class Ozellikli:
    """Erisim araya giren iki govdeye ugrar."""

    def __init__(self):
        self._deger = 0

    @property
    def deger(self):
        KAYIT.append("okuma")
        return self._deger

    @deger.setter
    def deger(self, yeni):
        KAYIT.append("yazma")
        if yeni < 0:
            raise ValueError("deger negatif olamaz")
        self._deger = yeni


class Alan:
    """Betimleyici: erisim mantigini kendi sinifinda tasir."""

    def __set_name__(self, sahip, ad):
        self.ad = "_" + ad

    def __get__(self, nesne, sahip=None):
        KAYIT.append("okuma")
        return getattr(nesne, self.ad)

    def __set__(self, nesne, yeni):
        KAYIT.append("yazma")
        if yeni < 0:
            raise ValueError("deger negatif olamaz")
        setattr(nesne, self.ad, yeni)


class Betimleyicili:
    """Ayni denetimi bir betimleyiciye devreder."""

    deger = Alan()

    def __init__(self):
        self.deger = 0


def olc(sinif):
    KAYIT.clear()
    n = sinif()
    KAYIT.clear()
    for _ in range(3):
        n.deger = n.deger + 1
    try:
        n.deger = -1
    except ValueError as e:
        hata = type(e).__name__
    else:
        hata = "yok"
    return list(KAYIT), n.deger, hata


print(f"{'sınıf':<14s} {'okuma':>6s} {'yazma':>6s} {'toplam gövde':>13s} "
      f"{'son değer':>10s} {'negatif yazma':>14s}")
for sinif in (Dogrudan, Ozellikli, Betimleyicili):
    kayit, son, hata = olc(sinif)
    print(f"{sinif.__name__:<14s} {kayit.count('okuma'):>6d} "
          f"{kayit.count('yazma'):>6d} {len(kayit):>13d} {son:>10d} {hata:>14s}")
```

```
sınıf           okuma  yazma  toplam gövde  son değer  negatif yazma
Dogrudan            0      0             0         -1            yok
Ozellikli           3      4             7          3     ValueError
Betimleyicili       3      4             7          3     ValueError
```

Aynı satırlar, üç ayrı bedel. `Dogrudan` yedi erişimde **0** gövde çalıştırıyor; erişim doğrudan
örneğin sözlüğüne gidiyor ve dönüyor. `Ozellikli` ve `Betimleyicili` aynı yedi erişimde **7**
gövde çalıştırıyor — üç okuma, dört yazma. Sayı erişim sayısının aynısıdır, çünkü araya giren
kod **her** erişimde çalışır, ilkinde ya da bazılarında değil.

Bedelin karşılığı son iki sütunda. Denetimsiz alanda negatif yazma **geçti** ve son değer
**-1** oldu; sınıf geçersiz bir durumda kaldı ve kimse bunu bilmiyor. Araya giren gövdenin
olduğu iki sınıfta aynı yazma `ValueError` ile **düştü** ve son değer **3**'te, yani son geçerli
değerinde kaldı. Önceki dersin bıraktığı açık burada kapanıyor: değişmezi koruyan tek yer bir
gövdedir ve `n.deger = -1` yazımını o gövdeye uğratmanın yolu alanı bir tanımın arkasına
almaktır.

Bu tasarımın en önemli özelliği çağıran tarafta **hiçbir şeyin değişmemesidir.** Üç sınıf da
`n.deger` yazımıyla kullanılıyor; alanın çıplak mı yoksa iki gövdeyle mi karşılandığı çağıran
taraftan görünmüyor. Bir alanı sonradan denetim altına almak, o alanı kullanan hiçbir satırı
değiştirmeden yapılabilir. Alanları önden getir–koy yöntemlerinin arkasına saklamanın gereksiz
olmasının nedeni budur: **çıplak alan, gerektiğinde arkasına gövde konabilecek bir alandır.**

İki tanım aynı sonucu veriyorsa aralarındaki seçim nedir? Özellik gövdelerini **sınıfın kendi
içinde** tutar; tek bir sınıfa özgü bir denetim için doğrudan yazımdır. Betimleyici mantığı
**ayrı bir sınıfa** taşır ve o sınıf birçok yerde yeniden kullanılabilir; aynı denetim on alanda
gerekiyorsa özellik on çift gövde ister, betimleyici on satır ister.

Aynı düzenin iki yaygın kullanımı daha vardır ve ikisi de ölçülen sayının aynısını öder.
Birincisi **salt okunur** alandır: yalnız okuma gövdesi yazılır, yazma gövdesi hiç yazılmaz ve
alana atama `AttributeError` ile düşer. Değeri kurulumda belirlenip sonra değişmemesi gereken
alanlar böyle bildirilir. İkincisi **hesaplanan** alandır: okuma gövdesi saklanmış bir değeri
döndürmek yerine onu her seferinde üretir. İkisi de dışarıdan bir alan gibi görünür ve
görünüşün yanıltıcı tarafı buradadır — `n.deger` yazımı bedava bir sözlük araması gibi okunur
ama arkasında koşan bir gövde vardır. Bir döngünün içinde aynı alanı bin kez okumak, bin kez
gövde çalıştırmak demektir. Bu yüzden araya giren gövdenin **ucuz** olması beklenir; pahalı bir
iş bir alan gibi sunulmaz, açık bir yöntem çağrısı olarak sunulur.

#### Betimleyici Veriyi Nerede Tutar

Betimleyicinin bir tuzağı vardır ve ilk ölçümdeki tanımın neden örneğin sözlüğüne yazdığını
açıklar. Betimleyici nesnesi **sınıf gövdesinde** var edilir; yani sınıf başına **bir**
tanedir ve bütün örnekler onu paylaşır. Veriyi kendi üzerinde tutan bir betimleyici, veriyi de
paylaştırmış olur.

- **SN56** — İki betimleyici aynı işi yapar; tek farkları veriyi nereye yazdıklarıdır.
- **SN57** — Üç örnek var edilir ve her biri kendi sırasını yazar; sonra üçü de okunur.
- **SN58** — Son sütun üç örneğin **kendi** sözlüklerindeki toplam giriş sayısıdır;
  içerikleri basılmaz.

```python
class KendindeTutan:
    """Veriyi betimleyici nesnesinin uzerinde tutar."""

    def __get__(self, nesne, sahip=None):
        return self.deger

    def __set__(self, nesne, yeni):
        self.deger = yeni


class OrnekteTutan:
    """Veriyi erisilen ornegin sozlugunde tutar."""

    def __set_name__(self, sahip, ad):
        self.ad = "_" + ad

    def __get__(self, nesne, sahip=None):
        return getattr(nesne, self.ad)

    def __set__(self, nesne, yeni):
        setattr(nesne, self.ad, yeni)


class KotuKutu:
    deger = KendindeTutan()


class IyiKutu:
    deger = OrnekteTutan()


print(f"{'sınıf':<10s} {'üç örneğin gördüğü':>20s} {'tek betimleyici':>16s} "
      f"{'örnek sözlüğünde':>17s}")
for sinif in (KotuKutu, IyiKutu):
    ucu = [sinif() for _ in range(3)]
    for i, n in enumerate(ucu):
        n.deger = i
    tanim = sinif.__dict__["deger"]
    print(f"{sinif.__name__:<10s} {str([n.deger for n in ucu]):>20s} "
          f"{str(all(type(n).__dict__['deger'] is tanim for n in ucu)):>16s} "
          f"{sum(len(n.__dict__) for n in ucu):>17d}")
```

```
sınıf        üç örneğin gördüğü  tek betimleyici  örnek sözlüğünde
KotuKutu              [2, 2, 2]             True                 0
IyiKutu               [0, 1, 2]             True                 3
```

Üç örnek üç ayrı değer yazdı. `KotuKutu`'da üçü de **son yazılanı** okuyor; ilk iki yazma
kayboldu ve örnek sözlüklerinde **0** giriş var. `IyiKutu`'da her örnek kendi yazdığını okuyor
ve **3** giriş açıldı. Orta sütun nedeni veriyor: iki sınıfta da üç örnek **tek** betimleyici
nesnesini paylaşıyor. Betimleyici veriyi kendi üzerine yazdığında, üç örneğin yazması aynı tek
nesnenin aynı alanına gidiyor.

Bu, ilk dersin sınıf gövdesindeki kap ölçümüyle aynı düzenektir ve başka bir kılıkta karşımıza
çıkıyor: **sınıf gövdesinde var edilen her şey örnekler arasında paylaşılır.** Betimleyici de
sınıf gövdesinde var edilir. Bu yüzden erişilen örneği ilk argüman olarak alır; verisini
saklayacağı yer o örnektir, kendisi değil.

Örneğe yazarken kullanılacak adın nereden geldiği de ayrı bir sorudur. Betimleyici sınıf
gövdesinde `deger = ...` biçiminde bağlanır ama nesnenin kendisi bu adı bilmez; kendisine
verilen adı öğrenmesi için sınıf kurulurken çağrılan bir kanca vardır. İlk ölçümdeki tanım da,
buradaki `OrnekteTutan` da o kancayı yazar ve örnekte kullanacağı adı ondan üretir. Kanca
yazılmazsa ad elle verilmek zorunda kalır ve aynı betimleyici iki alanda kullanıldığında iki
alan aynı yere yazar — `KotuKutu`'nun sonucunun bir başka biçimi.

"Kim" sorusunun bu ölçümdeki yanıtı iki katmanlıdır. Çağrıyı yanıtlayan **betimleyicinin
sınıfıdır** — `KotuKutu`'nun kendi gövdesinde `deger` için hiçbir gövde yoktur. Ama yanıtın
hangi veriye bakacağını yanıtlayan taraf değil, **verinin yazıldığı yer** belirler. İkisi
ayrıştığında ölçüm de ayrışır.

#### İki Kanca, İki Sıklık

Erişimi yakalamanın son biçimi tek tek alanlar için değil, **bütün** öznitelik erişimi
içindir. İki kanca vardır ve karıştırılmaları kolaydır: biri her erişimde çalışır, öteki
yalnız normal arama başarısız olduğunda çalışır.

- **SN59** — Tek sınıf iki kancayı birden yazar; her ikisi de yalnız kayıt tutar ve normal
  aramayı bozmaz.
- **SN60** — Dört ad okunur: ikisi örnek sözlüğünde vardır, ikisi hiçbir yerde yoktur.
  Kurulumda oluşan kayıtlar temizlenir.
- **SN61** — Ölçülen şey her kancanın **kaç kez** çalıştığıdır; dönen değerler basılmaz.

```python
IZ = []


class Kancali:
    """Iki kanca: biri her erisimde, oteki yalniz arama basarisiz olunca."""

    def __init__(self):
        self.birinci = 1
        self.ikinci = 2

    def __getattribute__(self, ad):
        IZ.append(("her erişim", ad))
        return object.__getattribute__(self, ad)

    def __getattr__(self, ad):
        IZ.append(("bulunamadı", ad))
        return None


n = Kancali()
IZ.clear()
OKUMALAR = ("birinci", "ikinci", "ucuncu", "dorduncu")
for ad in OKUMALAR:
    getattr(n, ad)

print(f"{'okunan ad':<12s} {'örnekte var':>12s} {'her erişim kancası':>19s} "
      f"{'bulunamadı kancası':>19s}")
for ad in OKUMALAR:
    print(f"{ad:<12s} {str(ad in ('birinci', 'ikinci')):>12s} "
          f"{sum(1 for k, a in IZ if k == 'her erişim' and a == ad):>19d} "
          f"{sum(1 for k, a in IZ if k == 'bulunamadı' and a == ad):>19d}")

print()
print(f"okuma {len(OKUMALAR)}, her erişim kancası "
      f"{sum(1 for k, _ in IZ if k == 'her erişim')} kez, bulunamadı kancası "
      f"{sum(1 for k, _ in IZ if k == 'bulunamadı')} kez çalıştı")
```

```
okunan ad     örnekte var  her erişim kancası  bulunamadı kancası
birinci              True                   1                   0
ikinci               True                   1                   0
ucuncu              False                   1                   1
dorduncu            False                   1                   1

okuma 4, her erişim kancası 4 kez, bulunamadı kancası 2 kez çalıştı
```

Dört okumada birinci kanca **4**, ikincisi **2** kez çalıştı. Sayılar iki kancanın yerini
söylüyor: birincisi aramanın **önündedir** ve okunan ad ne olursa olsun çalışır; ikincisi
aramanın **arkasındadır** ve yalnız arama boş döndüğünde devreye girer.

Ayrımın pratik sonucu maliyet ve risktir. Her erişimde çalışan kanca yazıldığında sınıfın
**bütün** öznitelik okumaları — yöntem erişimleri dahil — o gövdeden geçer; ölçümdeki dört
okuma dörde çıkmıştır, ama gerçek bir sınıfta bu sayı sınıfın yaptığı her iş kadardır. Üstelik
gövdenin içinde `self.` yazımı kullanmak kancayı yeniden tetikler ve sonsuz bir zincir kurar;
ölçümdeki gövdenin ortak tabanın aramasını doğrudan çağırmasının nedeni budur.

Yalnız bulunamayan adlarda çalışan kanca ise ölçüde ucuzdur: var olan adların yolunda hiç
görünmez. Var olmayan adları karşılamak, bir nesneye devredilmiş erişim sunmak ya da eksik
bir alanı hesaplayarak üretmek için doğru olan da budur. Kural şudur: **var olanı denetlemek
için alan başına tanım, var olmayanı karşılamak için bulunamadı kancası.**

Üç ölçüm birlikte okunduğunda öznitelik erişiminin tek bir sözlük araması olmadığı görülüyor.
Bir ad okunduğunda önce sınıfın çözümleme sırasına bakılır; orada **yazma gövdesi de olan** bir
betimleyici varsa onun okuma gövdesi çalışır ve örneğin sözlüğü hiç sorulmaz. Böyle bir tanım
yoksa örneğin sözlüğüne bakılır. Orada da yoksa sınıflardaki sıradan değerlere ve yalnız okuma
gövdesi olan tanımlara düşülür ve en sonda bulunamadı kancası devreye girer. Sıranın bu
düzeni ilk ölçümdeki denetimin neden atlanamadığını da açıklar: örnekte aynı adla bir giriş
açılsa bile, yazma gövdesi olan tanım okumada **öne geçer**. İlk dersin ölçtüğü "önce
örnek, sonra sınıf" kuralı bu zincirin **ortasıdır**; bu dersin eklediği şey zincirin başına ve
sonuna yazılabilen gövdelerdir. Erişimi denetlemek, bu zincire bir halka koymaktır — ve her
halka her erişimde ödenir.

Zincirin bir asimetrisini de kaydetmek gerekir: yazma tarafı okuma tarafı kadar zengin
değildir. Bir betimleyici yazmayı da yakalayabilir, ama örneğin sözlüğüne doğrudan yazan bir
kod sınıfın hiçbir gövdesine uğramaz. Üçüncü dersin ölçtüğü **0** engel bu dersten sonra da
geçerlidir; değişen tek şey, alışılmış yazımın artık bir gövdeye uğramasıdır.

#### Özet

- Araya giren tanım her erişimde çalışır: yedi erişim doğrudan alanda **0**, özellikte ve
  betimleyicide **7** gövde çalıştırır — üç okuma, dört yazma.
- Bedelin karşılığı denetimdir: geçersiz yazma çıplak alanda **geçer** ve son değer **-1**
  olur, iki tanımlı sınıfta `ValueError` ile **düşer** ve son değer **3**'te kalır.
- Çağıran taraf üç sınıfta da aynı satırları yazar; bu yüzden çıplak bir alan, kullanıcılarını
  hiç değiştirmeden sonradan bir gövdenin arkasına alınabilir.
- Betimleyici sınıf gövdesinde bir kez var edilir ve bütün örneklerce paylaşılır: veriyi kendi
  üzerinde tutan tanımda üç örnek **[2, 2, 2]** okur ve örnek sözlüklerinde **0** giriş açılır,
  örnekte tutan tanımda **[0, 1, 2]** okunur ve **3** giriş açılır.
- İki kanca iki ayrı sıklıkta çalışır: dört okumada her erişim kancası **4**, yalnız
  bulunamayan adlarda çalışan kanca **2** kez devreye girer.

#### Sonraki Adım

Buraya kadar yazılan her gövde elle yazıldı. Önceki ders on bir sözdizim için on iki gövde
istedi; bu ders tek bir alanın denetimi için iki gövde daha ekledi. Oysa yazdığımız sınıfların
çoğu aynı kalıbı tekrarlar: birkaç alan taşırlar, alanlarıyla karşılaştırılırlar ve
alanlarıyla basılırlar. Sonraki ders bu tekrarı kısa bir tanımla değiştirmenin ne ürettiğini
ölçer: kısa tanım hangi özel yöntemleri sınıfın sözlüğüne **gerçekten** yazar, elle yazılmış
karşılığıyla yan yana konduğunda hangileri eksik kalır ve tanıma eklenen her seçenek sözlüğe
kaç yeni ad koyar?

### Veri Sınıfları

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/veri-siniflari

Dört kaynak satırlık kısa tanım elle yazılan on satırın yazdığı 4 özel yöntemin aynısını yazar; sıralama seçeneği 8'e, dondurma 6'ya, ikisi birlikte 10'a çıkarır ve dondurulan sınıfta beş işlemin 3'ü düşer.

Önceki iki ders yazılan gövdeleri saydı: on bir sözdizim için on iki gövde, tek bir alanın
denetimi için iki gövde daha. Oysa yazdığımız sınıfların çoğu aynı kalıbı tekrarlar. Birkaç
alan taşırlar, kurulumda o alanları doldururlar, alanlarıyla karşılaştırılırlar ve alanlarıyla
basılırlar. Bu kalıp her sınıfta elle yazıldığında hem uzar hem de her yazımda bir alanı
unutma olasılığı taşır.

Standart kitaplık bu kalıp için kısa bir tanım biçimi sunar. Bu dersin sorusu kolaylık değil,
ölçüdür: kısa tanım sınıfın sözlüğüne **hangi** özel yöntemleri gerçekten yazar, elle yazılmış
karşılığıyla yan yana konduğunda hangileri eksik kalır ve tanıma eklenen her seçenek sözlüğe
kaç yeni ad koyar?

#### Kısa Tanım Ne Yazıyor

Beş sınıf aynı iki alanı taşır: bir dizgi ve bir tam sayı. `ElleYazilan` kurulum, temsil ve
eşitlik gövdelerini elle yazar. Kalan dördü **veri sınıfı (data class)** tanımıdır: alanlar
adlarıyla ve açıklamalarıyla bildirilir, gövdeler bildirilmez. Dört tanım birbirinden yalnız
verilen seçeneklerle ayrılır — biri varsayılan, biri sıralama ister, biri dondurulmuştur, biri
ikisini birden.

Ölçüm on özel yöntem adını her sınıfın **kendi sözlüğünde** arar. Atalardan gelen adlar sayıya
girmez; sorulan şey kısa tanımın o sınıfa ne **yazdığıdır**.

- **SN62** — Beş sınıfın alanları aynıdır; adları, sıraları ve açıklamaları da aynıdır.
- **SN63** — "Kaynak satırı" satırı her tanımın kaç dolu kaynak satırı tuttuğunu sayar; boş
  satırlar sayılmaz, süsleyici satırı sayılır.
- **SN64** — Aranan on ad yalnız sınıfın kendi sözlüğünde aranır; `saglayan` kullanılmaz,
  çünkü ölçülen şey adın **yazılıp yazılmadığıdır**, nereden geldiği değil.
- **SN65** — "Karma değeri" satırı adın varlığına değil, **değerinin boş olup olmadığına**
  bakar; önceki dersin ölçtüğü boşaltma burada yeniden görünür.

```python
"""Kisa tanim sinifin sozlugune hangi ozel yontemleri yaziyor."""

import inspect
from dataclasses import dataclass


class ElleYazilan:
    def __init__(self, ad, sayi):
        self.ad = ad
        self.sayi = sayi

    def __repr__(self):
        return f"ElleYazilan(ad={self.ad!r}, sayi={self.sayi!r})"

    def __eq__(self, o):
        if not isinstance(o, ElleYazilan):
            return NotImplemented
        return (self.ad, self.sayi) == (o.ad, o.sayi)


@dataclass
class Varsayilan:
    ad: str
    sayi: int


@dataclass(order=True)
class Sirali:
    ad: str
    sayi: int


@dataclass(frozen=True)
class Donmus:
    ad: str
    sayi: int


@dataclass(frozen=True, order=True)
class DonmusSirali:
    ad: str
    sayi: int


SINIFLAR = (ElleYazilan, Varsayilan, Sirali, Donmus, DonmusSirali)
ARANAN = ("__init__", "__repr__", "__eq__", "__hash__", "__lt__", "__le__",
          "__gt__", "__ge__", "__setattr__", "__delattr__")


def satir(sinif):
    """Tanimin kac dolu kaynak satiri tuttugu."""
    return sum(1 for s in inspect.getsource(sinif).splitlines() if s.strip())


print(f"{'özel yöntem':<13s}" + "".join(f"{s.__name__:>13s}" for s in SINIFLAR))
for ad in ARANAN:
    print(f"{ad:<13s}"
          + "".join(f"{('yazılı' if ad in s.__dict__ else '.'):>13s}"
                    for s in SINIFLAR))

print(f"{'toplam yazılı':<13s}"
      + "".join(f"{sum(1 for a in ARANAN if a in s.__dict__):>13d}"
                for s in SINIFLAR))
print(f"{'kaynak satırı':<13s}" + "".join(f"{satir(s):>13d}" for s in SINIFLAR))
print(f"{'karma değeri':<13s}"
      + "".join(f"{('yok' if s.__hash__ is None else 'var'):>13s}"
                for s in SINIFLAR))
```

```
özel yöntem    ElleYazilan   Varsayilan       Sirali       Donmus DonmusSirali
__init__            yazılı       yazılı       yazılı       yazılı       yazılı
__repr__            yazılı       yazılı       yazılı       yazılı       yazılı
__eq__              yazılı       yazılı       yazılı       yazılı       yazılı
__hash__            yazılı       yazılı       yazılı       yazılı       yazılı
__lt__                   .            .       yazılı            .       yazılı
__le__                   .            .       yazılı            .       yazılı
__gt__                   .            .       yazılı            .       yazılı
__ge__                   .            .       yazılı            .       yazılı
__setattr__              .            .            .       yazılı       yazılı
__delattr__              .            .            .       yazılı       yazılı
toplam yazılı            4            4            8            6           10
kaynak satırı           10            4            4            4            4
karma değeri           yok          yok          yok          var          var
```

İlk iki sütun karşılaştırmanın kendisidir. Elle yazılan tanım **10** kaynak satırında **4** ad
yazıyor; kısa tanım **4** satırda aynı **4** adı yazıyor. Yazılan adlar birebir aynıdır —
kısa tanım eksik bir şey bırakmıyor, fazladan bir şey de eklemiyor. Ölçünün asıl söylediği
şudur: kısa tanımın kazandırdığı yalnız altı satır değildir. Elle yazılan sınıfta her alan adı
**üç gövdenin üçünde de** yazılır — kurulumda, temsilde ve eşitlikte. Yeni bir alan
eklendiğinde üç gövdenin üçü de güncellenmek zorundadır; kısa tanımda alan **bir kez**
bildirilir. Unutulan bir güncellemeyle sessizce eksik karşılaştıran bir eşitlik gövdesi, bu
kalıbın en pahalı hatasıdır ve hiçbir zaman istisna üretmez.

Dördüncü satır önceki dersin ölçtüğü düzeneği yeniden gösteriyor. Beş sınıfın beşinde de
`__hash__` adı sözlükte **yazılıdır**, ama alt satır ilk üçünde değerinin **boş** olduğunu
söylüyor. Eşitliği yazan her sınıf karma yeteneğini kaybeder; kısa tanım da bu kuralın dışında
değildir. Elle yazılan sınıf da aynı durumdadır ve aynı nedenle. Sonuç, veri sınıflarının en sık
şaşırtan yanıdır: değerle karşılaştırılabilen bir nesne, dondurulmadıkça bir kümeye ya da
sözlük anahtarına konamaz.

Seçenekler sözlüğe ne kattığını açıkça gösteriyor. Sıralama seçeneği **4** karşılaştırma
yöntemi ekler ve toplamı **8**'e çıkarır. Dondurma seçeneği yazma ve silme yollarını yakalayan
**2** yöntem ekler, toplamı **6** yapar ve karma değerini geri getirir. İkisi birlikte
verildiğinde toplam **10** olur — dört satırlık bir tanım, on gövdelik bir sınıf üretir.

Bu tablo kısa tanımın ne olduğunu da tanımlıyor: bir tanım değil, bir **yazma işlemidir.**
Sınıf gövdesindeki alan bildirimleri okunur ve karşılık gelen gövdeler sınıfın sözlüğüne
konur. Sözlüğe konan her ad, elle yazılabilecek olanın aynısıdır — bu yüzden kısa tanımla
yazılmış bir sınıfta herhangi bir yöntemi elle yazmak da onu **geçersiz kılar**, çünkü
gövdede zaten bulunan bir adın üzerine yazılmaz.

Yazılmayanları da saymak gerekir. Ölçüm on ad aradı ve dördüncü dersteki on bir sözdizimin
çoğu bu listede yok: uzunluk, yineleme, üyelik, sıra erişimi ve bağlam sözdizimleri kısa
tanımdan gelmez. Kısa tanım bir **kayıt** yazar, bir kap yazmaz. Bir veri sınıfının kap gibi
davranması isteniyorsa gerekli gövdeler yine elle yazılır.

Alanların nasıl bulunduğu da bir noktayı açıkta bırakıyor. Kısa tanım hangi adların alan
olduğunu, sınıf gövdesindeki **açıklamalardan** okur — `ad: str` yazımındaki tip adı alanın
varlığını bildirir. Bu tip adının çalışma zamanında ne yaptığı ayrı bir sorudur ve bu kursun
ikinci konusunda ölçülür; burada söylenebilecek olan, kısa tanımın onu yalnız bir **işaret**
olarak kullandığıdır.

#### Değiştirilebilir Varsayılan

Kısa tanımın yalnız yazdıkları değil, **reddettikleri** de ölçülebilir. Python Temelleri kursu
varsayılan değerin işlev tanımlanırken **bir kez** değerlendirildiğini ve değiştirilebilir bir
varsayılanın çağrılar arasında paylaşıldığını ölçmüştü; **o ölçüm tekrarlanmaz.** Burada
sorulan şey aynı yazımın iki tanım biçiminde ne ürettiğidir.

- **SN66** — Üç tanım aynı alanı aynı varsayılanla kurmayı dener; ilki elle yazılan sınıf,
  ötekiler kısa tanımdır.
- **SN67** — Kurulamayan tanım ünlem önekiyle **istisnanın adını** kaydeder ve o satırın
  ölçüm sütunları boş bırakılır.
- **SN68** — Kurulan her tanımdan üç örnek var edilir ve her biri kaba kendi sırasını ekler;
  paylaşım `is` ile gösterilir.

```python
from dataclasses import field


class ElleKap:
    """Varsayilan deger bir kez degerlendirilir; uc ornek onu paylasir."""

    def __init__(self, ogeler=[]):
        self.ogeler = ogeler


def kisa_tanim(varsayilan):
    """Ayni varsayilani kisa tanimla kurmayi dener."""
    try:
        @dataclass
        class Gecici:
            ogeler: list = varsayilan
    except ValueError as e:
        return f"!{type(e).__name__}", None
    return "kuruldu", Gecici


TANIMLAR = (
    ("elle yazılan, varsayılan []", "kuruldu", ElleKap),
    ("kısa tanım, varsayılan []", *kisa_tanim([])),
    ("kısa tanım, üretici işlev", *kisa_tanim(field(default_factory=list))),
)

print(f"{'tanım':<28s} {'tanım anı':>16s} {'üç örneğin gördüğü':>20s} "
      f"{'kap paylaşılıyor':>17s}")
for ad, durum, sinif in TANIMLAR:
    if sinif is None:
        print(f"{ad:<28s} {durum:>16s} {'—':>20s} {'—':>17s}")
        continue
    ucu = [sinif() for _ in range(3)]
    for i, n in enumerate(ucu):
        n.ogeler.append(i)
    print(f"{ad:<28s} {durum:>16s} {str([len(n.ogeler) for n in ucu]):>20s} "
          f"{str(ucu[0].ogeler is ucu[2].ogeler):>17s}")
```

```
tanım                               tanım anı   üç örneğin gördüğü  kap paylaşılıyor
elle yazılan, varsayılan []           kuruldu            [3, 3, 3]              True
kısa tanım, varsayılan []         !ValueError                    —                 —
kısa tanım, üretici işlev             kuruldu            [1, 1, 1]             False
```

İlk satır bilinen sonucu veriyor: elle yazılan tanım **kuruluyor**, üç örnek tek kabı
paylaşıyor ve her biri **3** öğe görüyor. Hata çalışma zamanında da ortaya çıkmıyor; program
sessizce yanlış çalışıyor.

İkinci satır kısa tanımın farkıdır. Aynı yazım **tanım anında** `ValueError` ile düşüyor;
sınıf hiç kurulamıyor ve hatanın ortaya çıkacağı yer, hatanın yazıldığı satırın kendisi
oluyor. Üçüncü satır kabul edilen yazımı gösteriyor: varsayılan bir değer değil, her örnek
için çağrılacak bir **üretici işlev** verildiğinde tanım kuruluyor ve üç örnek **[1, 1, 1]**
görüyor — kap paylaşılmıyor.

Bu, ilk dersin sınıf gövdesindeki kap ölçümüyle aynı düzenektir ve üçüncü kez karşımıza
çıkıyor. Kısa tanımın buradaki katkısı yeni bir kural getirmek değil, var olan kuralı
**erkene almaktır**: bilinen bir yanlış yazımı çalışma zamanına bırakmak yerine tanım anında
reddeder.

Reddin ne kadar dar olduğunu da yazmak gerekir. Kısa tanım yalnız bildiği birkaç
değiştirilebilir tipi tanır; kendi yazdığımız değiştirilebilir bir sınıf varsayılan olarak
verildiğinde tanım kurulur ve paylaşım ilk satırdaki sonucun aynısını verir. Yakalanan şey
genel bir kural değil, sık yapılan bir yazımdır. Kuralın kendisi değişmemiştir: **varsayılan
değer tanım anında bir kez var edilir** ve örneğe ait olması gereken her kap bir üretici
işlevle bildirilir.

#### Dondurmanın Ölçüsü

Son seçenek nesnenin yazılabilirliğini kapatır. Değişmezliğin gerekçesi ve değer nesnesi
kavramı Yazılım Tasarımı ve Mimari İlkeler müfredatında kuruldu; **o tartışma tekrarlanmaz.**
Burada ölçülen şey tek bir seçeneğin kaç işlemi kapattığı ve karşılığında ne açtığıdır.

- **SN69** — İki sınıf aynı iki alanı taşır; tek farkları dondurulmuş olup olmamalarıdır.
- **SN70** — Beş işlem denenir ve her biri **taze** bir örnek üzerinde çalışır; işlemler
  birbirini etkilemez.
- **SN71** — Son satır aynı değerleri taşıyan **iki ayrı** nesneyi karşılaştırır; kimlik `is`
  ile gösterilir ve **kimlik sayısı basılmaz.**

```python
from dataclasses import replace

ISLEMLER = (
    ("alana yazma", lambda n: setattr(n, "sayi", 9)),
    ("alan silme", lambda n: delattr(n, "sayi")),
    ("yeni alan açma", lambda n: setattr(n, "yeni", 1)),
    ("yerine yeni nesne", lambda n: replace(n, sayi=9)),
    ("kümeye koyma", lambda n: len({n})),
)


def dene(sinif, islev):
    try:
        islev(sinif("k", 1))
    except Exception as e:
        return f"!{type(e).__name__}"
    return "geçti"


print(f"{'işlem':<19s} {'Varsayilan':>21s} {'Donmus':>21s}")
for ad, islev in ISLEMLER:
    print(f"{ad:<19s} {dene(Varsayilan, islev):>21s} {dene(Donmus, islev):>21s}")

print()
for sinif in (Varsayilan, Donmus):
    dusen = sum(1 for _, f in ISLEMLER if dene(sinif, f).startswith("!"))
    print(f"{sinif.__name__}: beş işlemden düşen {dusen}, "
          f"geçen {len(ISLEMLER) - dusen}")

a, b = Donmus("k", 1), Donmus("k", 1)
print(f"ayrı iki nesne: a is b -> {a is b}, a == b -> {a == b}, "
      f"kümede kaç öğe -> {len({a, b})}")
```

```
işlem                          Varsayilan                Donmus
alana yazma                         geçti  !FrozenInstanceError
alan silme                          geçti  !FrozenInstanceError
yeni alan açma                      geçti  !FrozenInstanceError
yerine yeni nesne                   geçti                 geçti
kümeye koyma                   !TypeError                 geçti

Varsayilan: beş işlemden düşen 1, geçen 4
Donmus: beş işlemden düşen 3, geçen 2
ayrı iki nesne: a is b -> False, a == b -> True, kümede kaç öğe -> 1
```

Dondurma **3** işlemi kapatıyor ve **1** işlemi açıyor. Kapananların üçü de yazmadır: alana
yazma, alan silme ve yeni bir alan açma. Üçüncüsü ayrıca dikkat çekicidir — üçüncü dersteki
öznitelik kümesi bildirimiyle aynı kapıyı kapatır, ama farklı bir gerekçeyle. Açılan işlem
kümeye koymadır: dondurulmuş nesne karma değerine sahip olduğu için bir kapta anahtar
olabilir, sıradan veri sınıfı olamaz.

Dördüncü satır iki sınıfta da geçiyor ve dondurmanın nasıl kullanıldığını gösteriyor.
Değişmez bir nesne değiştirilmez, **yerine yenisi konur**; standart kitaplığın verdiği işlev
istenen alanları değiştirilmiş yeni bir nesne üretir. Yazma engellenmiştir, yeniden üretme
engellenmemiştir.

Son satır dondurulmuş bir veri sınıfının ne olduğunu tanımlıyor: `is` **False**, `==` **True**,
kümede **1** öğe. İki ayrı nesne, aynı değerleri taşıdıkları için birbirinin yerine geçer.
Kimlikle değil değerle tanımlanmak, bu üç sonucun toplamıdır — ve bu dersteki tanımın ürettiği
şey tam olarak budur.

Dondurmanın sınırını da yazmak gerekir, çünkü tablo onu göstermez. Kapatılan şey **alana
yazma** yoludur; alanın gösterdiği nesnenin kendisi değil. Dondurulmuş bir nesnenin alanı bir
liste tutuyorsa o listeye öğe eklemek hiçbir engelle karşılaşmaz — üçüncü dersteki ölçümün
son satırı burada aynen geçerlidir. Değişmezlik bir katman derinliğindedir; nesnenin gerçekten
değiştirilemez olması, alanlarının da değiştirilemez nesneler olmasına bağlıdır. Karma değeri
de aynı sınırı taşır: dondurulmuş bir sınıf alanında bir liste taşıyorsa kümeye koyma satırı
`TypeError` ile düşer, çünkü karma alanlardan üretilir.

#### Özet

- Kısa tanım **4** kaynak satırında, elle yazılan **10** satırın yazdığı **4** özel yöntemin
  aynısını sınıfın sözlüğüne yazar; yazılan adlar birebir aynıdır.
- Eşitliği yazan her sınıfta olduğu gibi kısa tanımda da `__hash__` adı yazılır ve değeri
  **boş** bırakılır; karma yeteneği ancak dondurmayla geri gelir.
- Seçenekler sözlüğe ad ekler: sıralama **4** karşılaştırma yöntemi ekleyip toplamı **8**'e,
  dondurma **2** yöntem ekleyip **6**'ya çıkarır, ikisi birlikte **10** yapar.
- Değiştirilebilir varsayılan elle yazılan tanımda **kurulur** ve üç örnek tek kabı paylaşıp
  **[3, 3, 3]** görür; kısa tanımda **tanım anında** `ValueError` ile düşer ve üretici işlevle
  yazıldığında **[1, 1, 1]** görülür.
- Dondurma beş işlemin **3**'ünü kapatır ve **1**'ini açar: üç yazma yolu düşer, kümeye koyma
  geçer, yerine yeni nesne koyma iki sınıfta da geçer.
- Dondurulmuş iki ayrı nesne için `is` **False**, `==` **True** ve kümede **1** öğe kalır.

#### Sonraki Adım

Bu ders bir sınıfın ne yazdığını sözlüğüne bakarak saydı; önceki dersler de yanıtı hep sınıf
ağacında aradı. İki yöntem de aynı varsayıma dayanıyor: bir nesnenin ne yapabildiğini bilmek
için onun **hangi sınıftan** geldiğini bilmek gerekir. Oysa dördüncü ders bunun aksini
göstermişti — katılım tipin adından değil, yazılan yöntemlerden geliyordu. `Sarmal` hiçbir
şeyden türemez ama dört yeteneğin dördünü de karşılar. Sonraki ders bu ikiliği sınamaya
çevirir: aynı nesne, **soy bağı arayan** bir sınamayla **yöntem arayan** bir sınamadan
hangisini geçer, soyut bir tabanın bildirdiği sözleşme hangi anda uygulanır ve yöntem arayan
sınama gerçekten neye bakar?

### Soyut Temel Sınıflar ve Protokoller

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/soyut-temel-siniflar-ve-protokoller

Üç adayın 1'i soy bağı sınamasını, 3'ü yöntem sınamasını geçer ve 3'ü de çağrıyı karşılar; iki sınamanın kör noktalarında üç adayın 3'ü kabul edilir ama çağrıyı yalnız 1'i karşılar.

Önceki ders bir sınıfın ne yazdığını sözlüğüne bakarak saydı; ondan öncekiler yanıtı sınıf
ağacında aradı. İki yöntem de aynı varsayıma dayanıyordu: bir nesnenin ne yapabildiğini bilmek
için **hangi sınıftan** geldiğini bilmek gerekir. Oysa dördüncü ders bunun aksini gösterdi —
katılım tipin adından değil, yazılan yöntemlerden geliyordu. İkinci dersin `Sarmal` sınıfı bu
ikiliğin somut hâlidir: hiçbir şeyden türemez ve dört yeteneğin dördünü de karşılar.

Bu ders ikiliği bir sınamaya çevirir. Bir sözleşmenin sağlandığını iki ayrı biçimde
sorabiliriz. **Nominal** sınama soy bağı arar: "bu nesne şu sözleşmeden türedi mi?"
**Yapısal** sınama yöntem arar: "bu nesnede şu adlar var mı?" Aynı nesne ikisinden hangisini
geçer ve geçtiği sınama çağrıyı gerçekten karşılayacağını söyler mi? Sorunun ölçülebilir
olması için her adayda üç şey ayrı ayrı sorulur: iki sınamanın sonucu ve **çağrının kendisi.**
Üçünün de aynı çıkmadığı yerler bu dersin bulgusudur.

#### İki Sınama, Bir Nesne

İki sözleşme kurulur ve ikisi de aynı iki yeteneği ister: `kaynak` ve `bicimle`. Birincisi bir
**soyut temel sınıftır (abstract base class)**; onu sağlayan bir sınıf ondan türemek
zorundadır. İkincisi bir **protokoldür**; sınama sırasında yalnız adların varlığına bakar.

Üç aday sınanır. `Belge` kalıtım ağacından gelir ve iki yeteneği de üç ayrı sınıftan devralır.
`Sarmal` hiçbir şeyden türemez ve iki yeteneği de kendi gövdesinde yazar. `Bildiren` soyut
tabandan türer ve iki gövdeyi de yazar.

- **SN72** — Kurgunun dört sınıflı ağacı ve `Sarmal` ortak tanımdaki biçimindedir; sözleşmeler
  ayrıca kurulur ve ağaca dokunulmaz.
- **SN73** — İki sözleşme de aynı iki adı ister; aralarındaki tek fark sınamanın neye
  bakmasıdır.
- **SN74** — Her aday için üç şey ayrı ayrı ölçülür: adların gerçekten bulunup bulunmadığı, iki
  sınamanın sonucu ve çağrının verdiği sonuç.
- **SN75** — Son sütun `bicimle()` çağrısının döndürdüğü dizgidir; ikinci derste ölçülen
  sonucun aynısıdır ve burada yeniden basılır.

```python
"""Nominal ve yapisal sozlesme: ayni nesne hangi sinamayi geciyor."""

from abc import ABC, abstractmethod
from typing import Protocol, runtime_checkable


class Kayit:
    def kaynak(self):
        return "Kayit"

    def bicimle(self):
        return f"<{self.kaynak()}>"


class Zamanli(Kayit):
    def damga(self):
        return "zaman"


class Imzali(Kayit):
    def kaynak(self):
        return "Imzali"

    def imza(self):
        return "imza"


class Belge(Zamanli, Imzali):
    pass


class Sarmal:
    def __init__(self):
        self.zamanli = Zamanli()
        self.imzali = Imzali()

    def kaynak(self):
        return self.imzali.kaynak()

    def damga(self):
        return self.zamanli.damga()

    def imza(self):
        return self.imzali.imza()

    def bicimle(self):
        return f"<{self.kaynak()}>"


class Bicimlenebilir(ABC):
    """Nominal sozlesme: soy bagi ister."""

    @abstractmethod
    def kaynak(self):
        ...

    @abstractmethod
    def bicimle(self):
        ...


@runtime_checkable
class BicimlenebilirYapi(Protocol):
    """Yapisal sozlesme: yontem adi arar."""

    def kaynak(self): ...

    def bicimle(self): ...


class Bildiren(Bicimlenebilir):
    """Sozlesmeyi soy bagiyla bildirir ve iki govdeyi de yazar."""

    def kaynak(self):
        return "Bildiren"

    def bicimle(self):
        return f"<{self.kaynak()}>"


ADAYLAR = (("Belge", Belge()), ("Sarmal", Sarmal()), ("Bildiren", Bildiren()))

print(f"{'aday':<10s} {'iki yöntem de var':>18s} {'soy bağı sınaması':>18s} "
      f"{'yöntem sınaması':>16s} {'bicimle() sonucu':>17s}")
for ad, n in ADAYLAR:
    print(f"{ad:<10s} "
          f"{str(all(hasattr(n, y) for y in ('kaynak', 'bicimle'))):>18s} "
          f"{str(isinstance(n, Bicimlenebilir)):>18s} "
          f"{str(isinstance(n, BicimlenebilirYapi)):>16s} {n.bicimle():>17s}")

nominal = sum(1 for _, n in ADAYLAR if isinstance(n, Bicimlenebilir))
yapisal = sum(1 for _, n in ADAYLAR if isinstance(n, BicimlenebilirYapi))
print(f"\naday {len(ADAYLAR)}, soy bağı sınamasını geçen {nominal}, "
      f"yöntem sınamasını geçen {yapisal}, çağrıyı karşılayan {len(ADAYLAR)}")
```

```
aday        iki yöntem de var  soy bağı sınaması  yöntem sınaması  bicimle() sonucu
Belge                    True              False             True          <Imzali>
Sarmal                   True              False             True          <Imzali>
Bildiren                 True               True             True        <Bildiren>

aday 3, soy bağı sınamasını geçen 1, yöntem sınamasını geçen 3, çağrıyı karşılayan 3
```

Üç adayın üçü de çağrıyı karşılıyor. Soy bağı sınamasını geçen **1**, yöntem sınamasını geçen
**3**. Aradaki iki nesne — `Belge` ve `Sarmal` — sözleşmenin istediği her şeyi yapıyor ve
nominal sınamadan geçemiyor; çünkü o sınama yapılan işe değil, **bildirilen soya** bakıyor.

İki adayın nominal sınamayı geçememesi ayrı nedenlere dayanıyor ve bu, sınamanın ne kadar dar
olduğunu gösteriyor. `Sarmal` sözleşmeden haberdar bile değil. `Belge` ise sözleşmenin istediği
iki gövdeyi kalıtımla taşıyor — ikinci derste ölçüldüğü gibi `bicimle` gövdesi `Kayit`'te,
`kaynak` gövdesi `Imzali`'de. İkisi de yerinde duruyor, ikisi de çalışıyor ve sınama yine
`False` veriyor.

Yapısal sınama bu iki nesneyi kabul ediyor, çünkü sorduğu soru başka: sözleşmenin istediği
adlar bu nesnede bulunuyor mu? Bulunuyorsa nereden geldiği önemsizdir — kendi gövdesinden,
kalıtımla ya da bileşimle. Sözleşme bir **soy** değil, bir **yüzey** olarak tanımlanmıştır.

İki sınamanın maliyeti de aynı değildir. Nominal sınama tek bir soy ilişkisine bakar; sonucu
sınıf kurulduğu anda bellidir ve her sınamada yeniden hesaplanmaz. Yapısal sınama sözleşmenin
istediği her adı nesnede aramak zorundadır ve sözleşme büyüdükçe aranan ad sayısı artar.
Ölçümdeki sözleşme iki ad istiyor; on ad isteyen bir sözleşmede aynı sınama on arama yapar.
Ayrım burada da yön değiştirmiyor: nominal sınama ucuz ve dar, yapısal sınama pahalı ve
geniştir.

Arayüz ve soyut sınıf seçimi, arayüzlerin istemciye göre daraltılması ve sözleşmenin alt
tiplerde korunması Yazılım Tasarımı ve Mimari İlkeler müfredatında kuruldu; **o tartışmalar
tekrarlanmaz.** Burada ölçülen tek şey, aynı nesnenin **hangi sınamayı geçtiğidir.**

Ölçüm bir seçim de dayatmıyor; iki sınamanın hangi durumda doğru olduğunu söylüyor. Sözleşmenin
iki tarafını da biz yazıyorsak nominal bildirim kazanç sağlar: bildirimi yapan sınıf ne
sağlamak zorunda olduğunu açıkça söyler ve eksik bıraktığında durdurulur. Sözleşmeyi
karşılayacak nesneleri **başkaları** yazacaksa nominal bildirim bir yük olur — kabul edilecek
her nesnenin bizim tanımımızdan türemesini istemek, `Belge` ve `Sarmal` gibi işi eksiksiz
yapan nesneleri dışarıda bırakmak demektir. Standart kitaplığın kap sözleşmelerinin çoğu bu
yüzden gövde arayan bir kanca taşır: bir sınıf gerekli yöntemleri yazdığında, hiçbir soy bağı
kurmadan sözleşmenin sağlayıcısı sayılır.

#### Sözleşme Ne Zaman Uygulanır

Nominal sözleşmenin bir de uygulama tarafı vardır. Soyut bir tabandan türeyen bir sınıf,
bildirilen gövdelerin hepsini yazmak zorundadır. Ama bu zorunluluk hangi anda denetlenir —
sınıf tanımlanırken mi, yoksa örnek var edilirken mi?

- **SN76** — İki sınıf da aynı soyut tabandan türer; biri iki gövdeyi de yazar, öteki yalnız
  birini.
- **SN77** — "Tanım anı" sütunu bloğun çalışmasından okunur: iki sınıf da tanımlanabildiği
  için değeri sabittir. "Örnek var etme" sütunu çağrının sonucudur ve düşen çağrı ünlem
  önekiyle **istisnanın adını** kaydeder.

```python
class Eksik(Bicimlenebilir):
    """Sozlesmeyi bildirir ama iki govdeden yalniz birini yazar."""

    def kaynak(self):
        return "Eksik"


def dene(islev):
    try:
        islev()
    except Exception as e:
        return f"!{type(e).__name__}"
    return "geçti"


print(f"{'sınıf':<10s} {'tanım anı':>10s} {'örnek var etme':>16s} "
      f"{'yazılmamış yöntem':>18s}")
for sinif in (Bildiren, Eksik):
    print(f"{sinif.__name__:<10s} {'kuruldu':>10s} {dene(sinif):>16s} "
          f"{str(sorted(sinif.__abstractmethods__)):>18s}")
```

```
sınıf       tanım anı   örnek var etme  yazılmamış yöntem
Bildiren      kuruldu            geçti                 []
Eksik         kuruldu       !TypeError        ['bicimle']
```

Eksik sınıf **tanımlanabiliyor**. Hata tanım anında değil, ilk örnek var edilmeye
çalışıldığında alınıyor ve son sütun eksiğin adını veriyor. Ayrım pratikte önemlidir: eksik
sınıf bir dosyada durabilir, içe aktarılabilir, başka sınıflara taban olabilir. Kendisi hiç
var edilmediği sürece hiçbir şey olmaz — nitekim soyut tabanın kendisi de bu yüzden var
edilemez. Denetimin bu noktaya konması yerinde bir seçimdir: eksik bir sınıf hâlâ yararlı bir
tanım olabilir, ama eksik bir **nesne** sözleşmeyi taşıdığını iddia ederken çağrıyı
karşılayamaz. Durdurulan şey tanım değil, iddiadır.

Bu, uygulanan tek sözleşme olması bakımından kayda değerdir. Üçüncü ders adlandırmanın
**0** erişim engellediğini ölçmüştü; burada bildirilen sözleşme gerçekten uygulanıyor ve eksik
bir gerçekleştirim çalışma zamanında durduruluyor. Uygulama noktası ise dar: yalnız **gövdenin
varlığı** sınanır. Yazılan gövdenin doğru şeyi yapıp yapmadığı, doğru değeri döndürüp
döndürmediği bu sınamaya girmez.

Denetimin örnek var etmeye bağlanması bir kolaylık da sağlıyor. Yazılmamış yöntemlerin kümesi
her alt sınıf için yeniden hesaplanır; bir sınıf ara katman olarak kalıp bazı gövdeleri
bilerek yazmayabilir ve zincirin sonundaki sınıf eksikleri tamamlar. Ara sınıf var
edilmediği sürece hiçbir engelle karşılaşmaz. Sözleşme böylece birden çok sınıfa
bölünebilir — denetlenen şey bir sınıfın kendi gövdesi değil, çözümleme sırası boyunca
toplanan gövdelerin **tamamıdır.**

#### İki Sınamanın Kör Noktaları

Her iki sınamanın da kabul ettiği ama çağrıyı karşılamayan nesneler kurulabilir. İki kör nokta
birbirinin aynası gibidir: nominal sınama soyu okur ve gövdeye bakmaz; yapısal sınama adı
okur ve imzaya bakmaz.

- **SN78** — Birinci aday hiçbir gövde yazmaz ve sözleşmeye **elle kaydedilir**; kayıt
  yalnızca soy bilgisini değiştirir.
- **SN79** — İkinci aday iki adı da taşır ama iki yöntemi de **fazladan bir argüman** ister;
  adlar doğru, çağrı biçimi yanlıştır.
- **SN80** — Üçüncü aday önceki ölçümün doğru sınıfıdır ve karşılaştırma tabanı olarak
  eklenmiştir. Çağrı sütunu her adayda argümansız yapılır.

```python
class Bos:
    """Hicbir govde yazmaz; sozlesmeye elle kaydedilir."""


Bicimlenebilir.register(Bos)


class YanlisImza:
    """Iki adi da tasir ama ikisi de fazladan bir arguman ister."""

    def kaynak(self, ek):
        return f"YanlisImza-{ek}"

    def bicimle(self, ek):
        return f"<{self.kaynak(ek)}>"


KOR_NOKTALAR = (("Bos", Bos()), ("YanlisImza", YanlisImza()),
                ("Bildiren", Bildiren()))

print(f"{'aday':<12s} {'soy bağı sınaması':>18s} {'yöntem sınaması':>16s} "
      f"{'bicimle() çağrısı':>18s}")
for ad, n in KOR_NOKTALAR:
    print(f"{ad:<12s} {str(isinstance(n, Bicimlenebilir)):>18s} "
          f"{str(isinstance(n, BicimlenebilirYapi)):>16s} "
          f"{dene(n.bicimle) if hasattr(n, 'bicimle') else '!AttributeError':>18s}")

kabul = sum(1 for _, n in KOR_NOKTALAR
            if isinstance(n, Bicimlenebilir) or isinstance(n, BicimlenebilirYapi))
karsilayan = sum(1 for _, n in KOR_NOKTALAR
                 if hasattr(n, "bicimle") and dene(n.bicimle) == "geçti")
print(f"\naday {len(KOR_NOKTALAR)}, en az bir sınamayı geçen {kabul}, "
      f"çağrıyı karşılayan {karsilayan}")
```

```
aday          soy bağı sınaması  yöntem sınaması  bicimle() çağrısı
Bos                        True            False    !AttributeError
YanlisImza                False             True         !TypeError
Bildiren                   True             True              geçti

aday 3, en az bir sınamayı geçen 3, çağrıyı karşılayan 1
```

Üç adayın **3**'ü en az bir sınamayı geçiyor; çağrıyı karşılayan **1**. İlk satır nominal
sınamanın körlüğüdür: `Bos` hiçbir gövde yazmaz, sözleşmeye elle kaydedilmiştir ve sınama
`True` verir. Kayıt yalnız soy bilgisini değiştirir; yazılan gövdeleri hiç sormaz. Çağrı
`AttributeError` ile düşer — sınamayı geçmiş bir nesne, aranan adı bile taşımıyor.

İkinci satır yapısal sınamanın körlüğüdür. `YanlisImza` iki adı da taşıyor ve sınama `True`
veriyor; ama iki yöntem de fazladan bir argüman istiyor ve argümansız çağrı `TypeError` ile
düşüyor. Yapısal sınama **adın varlığına** bakar; parametrelerine, dönüş değerine ya da
gövdesinin ne yaptığına bakmaz. Yüzeyin adı eşleşiyor, biçimi eşleşmiyor.

İki satır birlikte dersin sonucunu veriyor. Nominal sınama **bildirilmiş bir niyeti**
doğrular; niyetin yerine getirildiğini doğrulamaz. Yapısal sınama **var olan adları** doğrular;
o adların çağrılabilir olduğunu doğrulamaz. Hiçbiri "bu nesne çağrıyı karşılar" demez ve
ikisinin de kabul ettiği bir nesne yanlış olabilir.

Bunun ölçülen karşılığı ilk tabloyla yan yana konduğunda tamamlanıyor. Birinci ölçümde üç
adayın **3**'ü çağrıyı karşılıyordu ve nominal sınama bunların **1**'ini kabul ediyordu —
sınama gereğinden **dardı**. Üçüncü ölçümde iki sınama birer nesneyi kabul etti ve o nesneler
çağrıyı karşılamadı — sınama gereğinden **genişti**. Aynı araç bir yönde eksik, öteki yönde
fazla eleme yapıyor. Doğru sözleşme, sınamanın kendisinde değil, sınamanın neyi ölçtüğünü
bilerek seçilmesindedir.

Kör noktaların kapanma yeri de bellidir ve ikisi için ayrıdır. Nominal sınamanın kör noktası
elle kayıt yazımına özgüdür; soy bağı kalıtımla kurulduğunda ikinci ölçümdeki denetim devreye
girer ve eksik gövdeli bir sınıfın örneği hiç var edilemez. Elle kayıt bu denetimi atlayan bir
bildirimdir ve sorumluluğu kaydı yapana bırakır. Yapısal sınamanın kör noktası ise
kapanmıyor: çalışma zamanında yapılan bir ad taraması, imzayı hiçbir koşulda göremez. İmzayı
görebilecek olan taraf, kodu **çalıştırmadan okuyan** bir taraftır.

Bu ayrım pratikte bir yazım kuralına dönüşür. Bir sınamanın geçmesi çağrının karşılanacağının
güvencesi değildir; sınama yalnız bir **ön eleme**dir. Gelen nesneyi sınamak, onu çağırmanın
yerine geçmez — ve sınamayı geçmiş bir nesnenin çağrısı yine de düşebileceği için, sınama ile
çağrı arasındaki boşluğun kim tarafından kapatıldığı ayrıca kararlaştırılmalıdır.

#### Özet

- Nominal sınama soy bağı arar, yapısal sınama yöntem adı arar; aynı üç adayın **1**'i
  birinciyi, **3**'ü ikinciyi geçer ve **3**'ü de çağrıyı karşılar.
- `Belge` ile `Sarmal` sözleşmenin istediği iki gövdeyi de taşır — biri kalıtımla, öteki
  bileşimle — ve ikisi de nominal sınamadan geçemez; sınama yapılan işe değil, bildirilen soya
  bakar.
- Soyut tabanın bildirdiği sözleşme **tanım anında değil**, ilk örnek var edilirken uygulanır:
  eksik sınıf kurulur, örneği `TypeError` ile düşer ve eksik gövdenin adı bildirilir.
- Nominal sınamanın kör noktası gövdedir: hiçbir gövde yazmayan bir sınıf sözleşmeye elle
  kaydedildiğinde sınamayı geçer ve çağrı `AttributeError` ile düşer.
- Yapısal sınamanın kör noktası imzadır: iki adı da taşıyan ama fazladan argüman isteyen bir
  sınıf sınamayı geçer ve çağrı `TypeError` ile düşer.
- Üç kör nokta adayının **3**'ü en az bir sınamayı geçer, çağrıyı karşılayan **1**'dir:
  sınamayı geçmek, çağrının karşılanacağını söylemez.

#### Sonraki Adım

Bu konu boyunca sözleşme hep **kodda** bildirildi. Soyut taban gövdelerin yazılmasını istedi ve
istemediğinde örnek var edilmedi; protokol adların bulunmasını istedi ve sınama sonucunu
çalışma zamanında verdi. İkisi de eksik kaldı, ama ikisi de **çalışan** birer denetimdi:
bildirilen şeyle var olan şey karşılaştırıldı ve sonuç bir değer olarak döndü.

Son ölçümün açtığı boşluk başka türden bir bildirimi gerektiriyor. `YanlisImza` sınamayı
geçti, çünkü sınamanın gördüğü tek şey adlardı; yöntemin **kaç argüman aldığı**, argümanların
neye benzediği ve ne döndürdüğü hiçbir sınamaya girmedi. Bu bilgiler koda yazılabilir —
Python bunun için bir yazım biçimi verir ve önceki dersin kısa tanımı zaten o yazımı alan
bildirimi olarak kullandı. Bir sonraki konu tam buradan başlar: sözleşme **yazıya**
döküldüğünde onu kim sınar, çalışma zamanı bu yazıyla ne yapar ve yazılan şeyle yapılan şey
ayrıştığında hata nerede görünür?

## Tip Katmanı

Fonksiyon ve değişken açıklamaları, yeniden kullanılabilir tip tanımı, denetleyicinin bulduğu ve bulamadığı hatalar, ve şema tabanlı çalışma zamanı doğrulaması.

### Tip İpuçları

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/tip-ipuclari

Açıklama koda yazılır, yorumlayıcı onu saklar ve sınamaz: beş çağrının dördü ihlallidir ve çalışma zamanında hata veren sayısı sıfırdır, ama açıklamaların tamamı çalışma zamanında okunabilir.

Önceki ders sözleşmeyi kodda bildirdi ve sınadı. Soy bağı arayan sınama bir sınıfın
bildirilmiş atasını sordu, yöntem arayan sınama nesnenin gerçekten taşıdığı adlara baktı;
ikisi de bir **çalıştırma** sırasında, elde duran nesne üzerinde sonuç verdi. Sözleşme
koddaydı ve sınayan da koddu.

Bu dersin sorusu sözleşmenin başka bir yazılış biçiminden doğar. Bir işlevin imzasına
"buraya bir dizgi gelir, buradan bir tam sayı çıkar" yazmak da bir sözleşmedir — ama bu
sözleşme bir sınıf ağacına ya da bir yöntem adına değil, **yazının kendisine** işlenmiştir.
Kursun ekseni burada da aynı: **sözü kim denetler?** Bu ders yanıtın bir katmanını ölçer,
en alttakini: çalışma zamanı.

#### Açıklamanın Yazılışı

**Tip ipucu (type hint)** bir adın hangi tipte değer taşımasının beklendiğini bildiren
sözleşmedir. **Açıklama (annotation)** o ipucunun koda yazılmış hâlidir: işlev
parametresinden sonra iki nokta ve tip, dönüş için `->` ve tip.

```python
def yas_yaz(ad: str, yas: int) -> str:
    return f"{ad}:{yas}"
```

Aynı yazım işlev dışında da geçerlidir. Bir sınıf gövdesinde `ad: str` bir alanı bildirir,
modül düzeyinde `sayac: int = 0` bir değişkeni bildirir. Dördüncü bir yer daha vardır: bir
işlevin gövdesindeki yerel değişken. Ölçüm bu dördünü ayırıyor, çünkü yazımın aynı olması
sonucun aynı olduğu anlamına gelmiyor.

Programlama Temelleri kursu tip dönüşümünü ve dinamik tiplemeyi kurdu; bir adın çalışma
zamanında hangi nesneye bağlandığı orada ölçüldü ve burada tekrarlanmaz. **TypeScript**
kursu da aynı soruyu, tip katmanı çalıştırmadan önce denetlenen bir dilde sordu. Buradaki
fark tek cümleyle şudur: Python'da açıklama **çalıştırmayı durdurmaz**; bu dersin ölçtüğü
şey tam olarak o durdurmamanın sayısıdır.

#### Yorumlayıcı Açıklamayla Ne Yapar

Yorumlayıcı açıklamayı okur, değerlendirir ve **saklar**. Sakladığı yer işlevin
`__annotations__` sözlüğüdür; sınıfın ve modülün de aynı adlı bir sözlüğü olur. Bu sözlük
sıradan bir sözlüktür ve çalışma zamanında okunabilir.

Yapmadığı şey ise sınamaktır. Çağrı geldiğinde parametreye bağlanan nesnenin açıklamaya
uyup uymadığına bakılmaz; uymayan bir nesne de bağlanır, işlev gövdesi de çalışır. Bunun
doğrudan bir sonucu vardır: açıklamaya yazılan şeyin bir **tip** olması bile gerekmez.
Ölçüm bunu da gösteriyor — dönüş açıklaması olarak bir sayı yazılmış bir işlev sorunsuz
tanımlanır, sorunsuz çağrılır ve o sayı sözlükte olduğu gibi durur.

Ölçüm iki soruyu ayrı ayrı soruyor. Birincisi: açıklama nereye yazılırsa saklanır?
İkincisi: açıklamaya uymayan çağrılar çalıştırıldığında kaçı hata verir?

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

- **TP1** — Kâhin kurgunun kendisidir: beş çağrının hangisinin tip ihlali, hangisinin alan
  ihlali taşıdığını dersin yazarı belirledi. Ölçüm bu sınıflandırmayı bir araçtan değil,
  kurgudan alır.
- **TP2** — **Tip ihlali**, bağlanan nesnenin açıklamadaki tipten olmamasıdır. **Alan
  ihlali (domain violation)**, nesnenin tipi doğru olduğu hâlde değerinin alan kuralını
  bozmasıdır: boş ad, negatif yaş.
- **TP3** — Beş çağrının ikisi tip ihlali, ikisi alan ihlali taşır, biri temizdir. Diziler
  bu kursun ortak kurgusundan gelir ve derste değiştirilmez.
- **TP4** — Çalışma zamanı katmanının ölçüsü, çağrının bir istisna fırlatıp fırlatmadığıdır.
  Gövde bir dizgi biçimlemesinden ibarettir ve biçimleme her tipi kabul eder; ölçülen şey
  gövdenin ne yaptığı değil, **açıklamanın çağrıya karışıp karışmadığıdır**.
- **TP5** — Saklama ölçümünde sayılan şey `__annotations__` sözlüğünün öğe sayısıdır; dönüş
  açıklaması da bir öğedir.
- **TP6** — Yerel değişken açıklaması için "hiçbir yerde" denmesi, işlev nesnesinde böyle
  bir kaydın bulunmaması demektir; ölçü `__annotations__` üzerinden alınır.
- **TP7** — Bileşik açıklama ölçümünde sorulan tek soru, saklanan nesnenin `isinstance`
  sınamasına doğrudan verilip verilemeyeceğidir. Sınama başarısız olursa değil, sınamanın
  kendisi bir istisna fırlatırsa "hayır" yazılır.
- **TP8** — Son satırdaki hata, açıklamanın değil **gövdenin** ürettiği hatadır; ölçüm bu
  ikisini ayırmak için o çağrıyı ayrıca çalıştırır.

#### Ölçüm

```python
"""Tip ipuclari: aciklama nerede saklanir, calisma zamaninda ne denetlenir."""
import sys

sayac: int = 0


def yas_yaz(ad: str, yas: int) -> str:
    return f"{ad}:{yas}"


class KayitAlani:
    ad: str
    yas: int = 0


def yerel_aciklama():
    gecici: int = 1
    return gecici


def saklanan(nesne):
    return len(getattr(nesne, "__annotations__", {}))


print(f"{'açıklamanın yeri':<22s} {'saklandığı yer':<24s} {'okunabilen':>10s}")
YERLER = (("işlev imzası", "islev.__annotations__", saklanan(yas_yaz)),
          ("sınıf gövdesi", "Sinif.__annotations__", saklanan(KayitAlani)),
          ("modül düzeyi", "modül __annotations__", saklanan(sys.modules[__name__])),
          ("işlev gövdesi", "hiçbir yerde", saklanan(yerel_aciklama)))
for yer, nerede, sayi in YERLER:
    print(f"  {yer:<20s} {nerede:<24s} {sayi:10d}")
print(f"işlev açıklaması: {yas_yaz.__annotations__}")


def deneme(x: "sayi olmayan bir aciklama") -> 3:
    return x


print(f"tip olmayan açıklama saklandı: {deneme.__annotations__}")
print(f"deneme(41) -> {deneme(41)}")

CAGRILAR = (
    ("ad", 30),            # uygun
    ("ad", "otuz"),        # tip ihlali: yas dizgi
    (7, 30),               # tip ihlali: ad sayi
    ("ad", -5),            # tip uygun, alan ihlali: yas negatif
    ("", 30),              # tip uygun, alan ihlali: ad bos
)


def alan_kurali(ad, yas):
    hatalar = []
    if isinstance(ad, str) and not ad:
        hatalar.append("ad bos")
    if isinstance(yas, int) and yas < 0:
        hatalar.append("yas negatif")
    return hatalar


def calisma_zamani(cagrilar):
    hata, sonuc = 0, []
    for arg in cagrilar:
        try:
            sonuc.append(yas_yaz(*arg))
        except Exception:
            hata += 1
            sonuc.append(None)
    return hata, sonuc


tip_ihlali = [i for i, a in enumerate(CAGRILAR)
              if not isinstance(a[0], str) or not isinstance(a[1], int)]
alan_ihlali = [i for i, a in enumerate(CAGRILAR) if alan_kurali(*a)]
hata, sonuc = calisma_zamani(CAGRILAR)

print()
print(f"{'çağrı':>6s} {'tip ihlali':>11s} {'alan ihlali':>12s} "
      f"{'çalışma zamanı':>15s} {'dönen değer':>13s}")
for i, arg in enumerate(CAGRILAR):
    print(f"{i:6d} {str(i in tip_ihlali):>11s} {str(i in alan_ihlali):>12s} "
          f"{'hata yok':>15s} {sonuc[i]!r:>13s}")
print()
print(f"çağrı {len(CAGRILAR)}, ihlalli {len(set(tip_ihlali) | set(alan_ihlali))}, "
      f"tip ihlali {len(tip_ihlali)}, alan ihlali {len(alan_ihlali)}, "
      f"çalışma zamanında hata veren {hata}")


def toplam(sayilar: list[int]) -> int:
    return sum(sayilar)


BILESIK = (("list[int]", list[int], [1, 2]),
           ("str | None", str | None, "ad"),
           ("dict[str, int]", dict[str, int], {"a": 1}),
           ("tuple[int, ...]", tuple[int, ...], (1, 2)))

print()
print(f"{'bileşik açıklama':<18s} {'isinstance ile sınanır':>23s}")
for ad, nesne, ornek in BILESIK:
    try:
        isinstance(ornek, nesne)
        sinanir = "evet"
    except TypeError:
        sinanir = "hayır"
    print(f"  {ad:<16s} {sinanir:>23s}")
print(f"toplam açıklaması: {toplam.__annotations__}")
try:
    toplam(["a", "b"])
    print("toplam(['a', 'b']) -> hata yok")
except TypeError:
    print("toplam(['a', 'b']) -> gövde hata verdi, açıklama değil")
```

```
açıklamanın yeri       saklandığı yer           okunabilen
  işlev imzası         islev.__annotations__             3
  sınıf gövdesi        Sinif.__annotations__             2
  modül düzeyi         modül __annotations__             1
  işlev gövdesi        hiçbir yerde                      0
işlev açıklaması: {'ad': <class 'str'>, 'yas': <class 'int'>, 'return': <class 'str'>}
tip olmayan açıklama saklandı: {'x': 'sayi olmayan bir aciklama', 'return': 3}
deneme(41) -> 41

 çağrı  tip ihlali  alan ihlali  çalışma zamanı   dönen değer
     0       False        False        hata yok       'ad:30'
     1        True        False        hata yok     'ad:otuz'
     2        True        False        hata yok        '7:30'
     3       False         True        hata yok       'ad:-5'
     4       False         True        hata yok         ':30'

çağrı 5, ihlalli 4, tip ihlali 2, alan ihlali 2, çalışma zamanında hata veren 0

bileşik açıklama    isinstance ile sınanır
  list[int]                          hayır
  str | None                          evet
  dict[str, int]                     hayır
  tuple[int, ...]                    hayır
toplam açıklaması: {'sayilar': list[int], 'return': <class 'int'>}
toplam(['a', 'b']) -> gövde hata verdi, açıklama değil
```

#### Saklanan ve Saklanmayan

Üst tablo dört yeri yan yana koyuyor ve üçü ile biri arasında keskin bir sınır var.

İşlev imzası **3** açıklama saklıyor: iki parametre ve dönüş. Sınıf gövdesi **2**, modül
düzeyi **1** saklıyor. Bu üç yerde açıklama, yazıldığı nesnenin bir özniteliğine dönüşüyor
ve program çalışırken oradan okunabiliyor.

İşlev gövdesindeki yerel açıklama ise **0** veriyor. `gecici: int = 1` satırı geçerli bir
Python satırıdır, atamayı yapar, hata vermez — ama geride bir kayıt bırakmaz. Yerel adın
ömrü çağrı ile sınırlıdır ve açıklamasının ömrü de o kadardır. Aynı işlev bin kez
çağrılsaydı bin ayrı yerel ad doğup ölecek, saklanan açıklama sayısı yine **0** kalacaktı.
Sınıf ve modül düzeyinde durum tersidir: oradaki açıklama bir kez, tanımın yapıldığı anda
işlenir ve nesnenin ömrü boyunca durur.

Buradan çıkan ayrım sonraki derslerin dayanağıdır: **kaynağa yazılan her açıklama çalışma
zamanında görünmez.** Kaynağı okuyan bir katman dört yerin dördünü de görür; çalışan
programı okuyan bir katman yalnız üçünü.

Tip olmayan açıklamanın satırı da aynı noktayı başka yönden vuruyor. Dönüş açıklaması
olarak yazılan `3` sözlükte `3` olarak duruyor ve `deneme(41)` çağrısı **41** döndürüyor.
Yorumlayıcı açıklamanın anlamlı olup olmadığına bakmıyor, çünkü açıklamayı yorumlayan bir
tarafı yok.

#### Dört İhlal, Sıfır Hata

Alt tablo dersin ana sayısını veriyor. Beş çağrının **ikisi** tip ihlali taşıyor: birinde
yaş yerine bir dizgi, öbüründe ad yerine bir sayı geçiyor. **İkisi** alan ihlali taşıyor:
yaş **-5**, ad boş dizgi. Toplam **4** çağrı ihlallidir ve biri temizdir.

Çalışma zamanı sütunu beş satırın beşinde de "hata yok" diyor. Sayı **0**. Dördüncü sütun
bunun nedenini de gösteriyor: her çağrı bir değer döndürüyor. `'ad:otuz'` üretiliyor, çünkü
biçimleme bir dizgiyi de kabul eder. `'7:30'` üretiliyor, çünkü biçimleme bir sayıyı da
kabul eder. `'ad:-5'` ve `':30'` üretiliyor, çünkü **-5** bir tam sayıdır ve boş dizgi bir
dizgidir.

**Bu ölçümde ihlalleri yakalayan katman yok.** Kim yakaladı sorusunun yanıtı bu derste
tektir ve boştur: çalışma zamanı **0** yakalar. Kâhin dört ihlali biliyor çünkü kurguyu biz
yazdık; program bilmiyor.

Bir itiraz burada haklıdır: gövde bir dizgi biçimlemesinden ibaret olduğu için hoşgörülü;
başka bir gövde yanlış tiple hata verirdi. Doğrudur, ama ölçülen şey bu değil. Öyle bir
gövde hata verseydi hatayı veren yine **gövdedeki işlem** olurdu, açıklama değil — ve hata
işlev çağrıldıktan sonra, işin bir bölümü çoktan yapılmışken çıkardı. Açıklamanın
sağlayabileceği tek şey çağrının **hiç başlamamasıydı**; ölçümde başlamayan çağrı yok.

İki ihlal türünün ayrı sayılması bir ayrıntı değil, kursun geri kalanının ekseni.
`ad: str` açıklaması boş dizgiyi dışarıda bırakmaz — boş dizgi bir dizgidir. `yas: int`
açıklaması negatif sayıyı dışarıda bırakmaz — **-5** bir tam sayıdır. **Tipin doğru olması
değerin geçerli olduğu anlamına gelmez** ve bu, açıklamaların bir eksiği değil, tanımıdır:
açıklama tipi bildirir, değeri değil.

#### Bileşik Açıklama Bir Sınama Değildir

Açıklama tek bir tip adından ibaret olmak zorunda değil. `list[int]` "tam sayı listesi",
`dict[str, int]` "anahtarları dizgi, değerleri tam sayı olan sözlük", `str | None` "dizgi
ya da hiçbiri", `tuple[int, ...]` "istenildiği kadar tam sayıdan oluşan demet" bildirir.
Dördü de geçerli açıklamadır ve dördü de `__annotations__` içinde bir nesne olarak durur —
son satır bunu gösteriyor: `toplam` işlevinin açıklaması `list[int]` nesnesini taşıyor.

Üçüncü tablo bu nesnelerin çalışma zamanındaki sınırını ölçüyor. Dördünden **üçü**
`isinstance` sınamasına doğrudan verilemiyor; sınama bir sonuç döndürmüyor, istisna
fırlatıyor. Yalnız `str | None` "evet" veriyor, çünkü birleşim yalnız iki tip adını yan
yana koyar ve içeriğe bakmayı gerektirmez.

Neden böyle olduğu bir tasarım sorusu değil, bir maliyet sorusudur. `isinstance(x, list)`
tek bir bakışta yanıtlanır; `isinstance(x, list[int])` bin öğeli bir listede bin sınama
demektir. Sınamanın bedeli kabın boyutuyla büyür ve dil bu bedeli sessizce üstlenmez.

Sonuç, dersin ana savını pekiştiriyor: **açıklama bir sınama değil, bir kayıttır.** Son
satır bunu doğrudan gösteriyor — `toplam(["a", "b"])` çağrısı hata veriyor, ama hatayı
veren açıklama değil **gövdedeki toplama işlemidir**. Açıklama olmasaydı da aynı hata aynı
noktada çıkardı; açıklama olduğu hâlde çağrı engellenmedi.

#### Sınanmayan Açıklama Ne İşe Yarar

Sıfır sayısı bir soruyu doğuruyor: sınanmayan bir bildirim neden yazılsın?

Yanıt çıktının ortasındaki satırda duruyor. `yas_yaz.__annotations__` çalışma zamanında
okunabiliyor ve `ad` için `str`, `yas` için `int`, dönüş için `str` veriyor. Açıklama
sınanmıyor ama **kayboluyor da değil**. Bir sözlükte, adı bilinen bir öznitelikte,
program çalışırken erişilebilir hâlde duruyor.

Bu, sonraki üç dersin tamamının dayandığı olgu. Açıklamayı okuyup tip uyuşmazlığı arayan
bir program yazılabilir; kaynağı hiç çalıştırmadan okuyup aynı işi yapan bir program da
yazılabilir; açıklamayı bir şema gibi kullanıp çağrı anında değeri sınayan bir katman da
yazılabilir. Üçünün de hammaddesi bu sözlüktür.

İkinci bir yarar okuyanadır. `def yas_yaz(ad, yas)` imzası çağıranın hangi sırayla neyi
vereceğini söylemez; `def yas_yaz(ad: str, yas: int) -> str` söyler. Bu bir denetim değil,
bir belgedir — ve belge olduğu için de yanlış olabilir. Açıklamayla gövdenin uyuşmasını
sağlayan hiçbir düzenek yoktur; uyuşmayı sağlayan şey, açıklamayı okuyan **başka bir
katmandır**.

Kaydın kendisi de korumalı değildir. `__annotations__` sıradan bir sözlüktür: okunabildiği
gibi değiştirilebilir de. Bir program çalışırken bir işlevin açıklamasını silebilir ya da
başka bir tiple değiştirebilir; işlevin gövdesi bundan etkilenmez ve çağrılar aynı sonucu
vermeye devam eder. Bu, kaydın güvenilmez olduğu anlamına gelmez — kaydın **bağlayıcı
olmadığı** anlamına gelir. Bağlayıcılık ancak kaydı okuyup bir karar veren bir katman
eklendiğinde doğar ve o katmanı bu kursun geri kalanı kurar.

#### Özet

- Tip ipucu bir adın hangi tipte değer taşıyacağını bildiren sözleşmedir; açıklama o
  ipucunun koda yazılmış hâlidir ve işlev imzasında, sınıf gövdesinde, modül düzeyinde ve
  işlev gövdesinde yazılabilir.
- Açıklama üç yerde saklanır ve bir yerde saklanmaz: işlev **3**, sınıf **2**, modül **1**
  öğe verir; işlev gövdesindeki yerel açıklama **0** verir ve geride kayıt bırakmaz.
- Yorumlayıcı açıklamayı saklar, sınamaz; açıklamanın bir tip olması bile gerekmez — dönüş
  açıklaması olarak yazılan bir sayı sözlükte olduğu gibi durur ve çağrı çalışır.
- Beş çağrının **4**'ü ihlallidir — **2** tip ihlali, **2** alan ihlali — ve çalışma
  zamanında hata veren sayısı **0**'dır. Bu ölçümde ihlalleri yakalayan katman yoktur.
- Bileşik açıklamaların dördünden **üçü** `isinstance` sınamasına doğrudan verilemez;
  açıklama bir sınama değil, bir kayıttır ve kayıt bağlayıcı değildir.
- Tipin doğru olması değerin geçerli olduğu anlamına gelmez: boş dizgi bir dizgidir, **-5**
  bir tam sayıdır. Açıklama tipi bildirir, değeri değil.

#### Sonraki Adım

`yas_yaz` işlevinin açıklaması iki somut tip adı taşıyor ve bu yüzden yalnız o iki tiple
çalışan bir sözleşme bildiriyor. Oysa çok sayıda işlev tipe kayıtsızdır: bir listenin ilk
öğesini veren işlev listenin neyle dolu olduğunu umursamaz, ama **döndürdüğü şeyin
listenin içindekiyle aynı tipte** olduğunu bilir. Bu bağ somut bir tip adıyla yazılamaz —
yazılırsa ya bağ kaybolur ya da işlev gereksiz yere daraltılır. Sonraki ders aynı işlevin
dört ayrı yazımını yan yana koyuyor ve tek bir sayıyı soruyor: her yazımın açıklaması, bir
çağrının dönüş tipini kaç kez bildirebiliyor?

### Genellikler ve Tip Değişkenleri

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/genellikler-ve-tip-degiskenleri

Aynı işlevin dört yazımı dört çağrı yerinde ayrı sonuç verir: açıklamasız yazım iki ihlali kaçırır, gevşek yazım ikisini bulur ama iki yanlış alarm üretir, genellikli yazım ikisini de bulur ve hiç yanlış alarm vermez.

Önceki ders açıklamanın saklandığını ve sınanmadığını ölçtü; beş çağrının dördü ihlalliydi
ve çalışma zamanında hata veren sıfırdı. Ölçülen işlevin açıklaması iki somut tip adı
taşıyordu: `ad: str`, `yas: int`. Böyle bir imza yalnız o iki tiple çalışan bir sözleşme
bildirir ve bildirdiği şey açıktır.

Çok sayıda işlev ise tipe kayıtsızdır. Bir listenin ilk öğesini veren işlev listenin neyle
dolu olduğunu umursamaz — ama umursamadığı hâlde bir şey bilir: **döndürdüğü şey, listenin
içindekiyle aynı tiptedir.** Bu bağ somut bir tip adıyla yazılamaz. Bu dersin sorusu, o
bağın yazılıp yazılamayacağı ve yazıldığında ne kazandırdığıdır. Ölçü yine kursun ekseninde:
**hangi yazım hangi ihlali bildirebiliyor?**

#### Somut Tip Adının İki Ucu

`ilk` işlevini yazmanın iki kolay yolu var ve ikisi de bir şey kaybettiriyor.

Birinci yol açıklamayı **gevşetmektir**: `def ilk(dizi: list) -> object`. Bu imza her
listeyi kabul eder, doğrudur ve hiçbir çağrıyı gereksiz yere reddetmez. Kaybettiği şey
bağdır — dönüş için "bir nesne" demek, çağıranın elindeki değer hakkında hiçbir şey
söylememektir.

İkinci yol açıklamayı **somutlaştırmaktır**: `def ilk(dizi: list[int]) -> int`. Bu imza
dönüşü tam olarak bildirir. Kaybettiği şey işlevin kendisidir — gövde her listeyle
çalıştığı hâlde imza yalnız tam sayı listelerini kabul eder ve dizgi listesiyle yapılan
geçerli bir çağrı da ihlal sayılır.

İkisinin ortası yok gibi görünüyor: ya bağı bırakacaksınız ya işlevi daraltacaksınız.
Genellik tam olarak bu ikilemi ortadan kaldıran yazımdır.

#### Tip Değişkeni

**Tip değişkeni (type variable)**, bir tip adının yerini tutan addır. `T = TypeVar("T")`
satırı böyle bir ad üretir; `T` kendisi bir tip değildir, imzada bir tipin **duracağı yeri**
gösterir.

```python
from typing import TypeVar

T = TypeVar("T")


def ilk(dizi: list[T]) -> T:
    return dizi[0]
```

Bu imza iki şey söylüyor. Birincisi: argüman herhangi bir tiple dolu bir listedir — kabul
edilen küme daralmadı. İkincisi: dönüş, o listenin içindekiyle **aynı** tiptedir. Aynı adın
iki yerde geçmesi bağı kuran şeydir; `T` bir yerde ne ise öbüründe de odur.

Aynı adı taşıyan bir imzaya sahip yapıya **genellik (generic)** denir. Genellik bir işlevde
olabildiği gibi bir sınıfta da olabilir: `Generic[T]`'ten türeyen bir sınıf, taşıdığı
değerin tipini imzalarına yayabilir.

Tip değişkeni tamamen serbest bırakılmak zorunda değil. **Bağlı** bir tip değişkeni bir üst
sınır bildirir — `TypeVar("Yazi", bound=str)` yalnız dizgi ve alt sınıflarını kabul eder.
**Sınırlı** bir tip değişkeni ise sonlu bir seçenek listesi bildirir:
`TypeVar("Sayisal", int, float)`. İkisinin de sınırı çalışma zamanında okunabilir; ölçüm bu
sınırların okunmakla sınanmak arasındaki farkı da gösteriyor.

#### Ölçümün Kurgusu

Ölçüm dört yazımı dört çağrı yerinde karşılaştırıyor. Her çağrı yeri iki şeyden oluşuyor:
`ilk` çağrısı ve onun sonucuna uygulanan bir işlem. İşlem sonuçtan bir tip **bekler** —
`+ 1` bir tam sayı, `.upper()` bir dizgi. Çağrı yerinin ihlalli olması, listenin öğe
tipiyle işlemin beklediği tipin uyuşmamasıdır.

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

- **TP9** — Kâhin kurgunun kendisidir: dört çağrı yerinden hangisinin ihlalli olduğunu
  listenin öğe tipi ile işlemin gerektirdiği tip belirler; ikisini de dersin yazarı yazdı.
- **TP10** — Dört çağrı yerinin **ikisi** ihlallidir. Küme küçük tutulmuştur, çünkü ölçülen
  şey ihlal oranı değil, **her yazımın bildirebildiği ihlal kümesidir**.
- **TP11** — Çıkarım adımı ders içinde modellenmiştir: bir dönüş açıklamasından çağrı
  yerinin dönüş tipini üretir ve tip değişkenini argümandaki öğe tipiyle değiştirir. Tam
  denetleyici bu derste kurulmaz.
- **TP12** — "Bildirilen", yazımın o çağrı yerinde bir sorun bildirmesidir; iki nedenden
  biriyle olur: argüman açıklamaya uymaz, ya da çıkarılan dönüş tipi işlemin beklediğinden
  başkadır.
- **TP13** — "Doğru", bildirimin gerçekten ihlalli bir çağrı yerine düşmesidir.
  "Kaçırılan", ihlalin bildirilmemesidir. "Yanlış alarm", uygun bir çağrı yerinde bildirim
  yapılmasıdır.
- **TP14** — `object` dönüşü hiçbir işlemin beklediği tiple aynı sayılmaz; gevşek yazımın
  dört çağrı yerinde de bildirim yapması bu kuralın sonucudur.
- **TP15** — Çalışma zamanı sütunu dört çağrı yerinin **gerçekten çalıştırılmasıyla**
  ölçülür ve yalnız tip kaynaklı istisnalar sayılır.
- **TP16** — Genel sınıf ölçümünde kimlik `is` ile gösterilir; hiçbir kimlik sayısı
  basılmaz.
- **TP17** — Bağlı ve sınırlı tip değişkenlerinin ölçüsü, sınırın okunabilir olması ve
  sınırı bozan bir çağrının çalışıp çalışmamasıdır. İki argümanlı ölçümde birleştirme adımı
  da ders içinde modellenmiştir: tip değişkeni ilk eşleşmede bağlanır, sonraki
  eşleşmelerde bağın tutarlılığı sınanır.

#### Ölçüm

```python
"""Genellikler: hangi yazim hangi ihlali bildirebiliyor."""
from typing import Generic, TypeVar

T = TypeVar("T")


def ilk(dizi):
    return dizi[0]


YAZIMLAR = (("açıklamasız", None, None),
            ("list -> object", list, object),
            ("list[int] -> int", list[int], int),
            ("list[T] -> T", list[T], T))

# cagri yeri: kaynak listenin oge tipi, sonraki islemin gerektirdigi tip
YERLER = (("ilk([1, 2]) + 1", int, int),
          ("ilk(['a', 'b']) + 1", str, int),
          ("ilk(['a', 'b']).upper()", str, str),
          ("ilk([1, 2]).upper()", int, str))


def kabul(param, oge):
    """Aciklama bu cagri yerinin argumanini kabul ediyor mu."""
    ic = getattr(param, "__args__", (None,))[0]
    return param is None or param is list or ic is T or ic is oge


def cikarim(donus, oge):
    """Donus aciklamasindan cagri yerinin donus tipi cikarilabiliyor mu."""
    if donus is None:
        return None
    return oge if donus is T else donus


print(f"{'yazım':<18s} {'çıkarılan':>10s} {'bildirilen':>11s} {'doğru':>6s}"
      f" {'kaçırılan':>10s} {'yanlış alarm':>13s}")
for ad, param, donus in YAZIMLAR:
    cikan = bildirilen = dogru = kacirilan = yanlis = 0
    for yer, oge, gereken in YERLER:
        gercek_ihlal = oge is not gereken
        c = cikarim(donus, oge)
        cikan += c is not None
        bildirim = (not kabul(param, oge)) or (c is not None and c is not gereken)
        bildirilen += bildirim
        dogru += bildirim and gercek_ihlal
        kacirilan += gercek_ihlal and not bildirim
        yanlis += bildirim and not gercek_ihlal
    print(f"  {ad:<16s} {f'{cikan}/4':>10s} {bildirilen:11d} {dogru:6d}"
          f" {kacirilan:10d} {yanlis:13d}")

GERCEK = (lambda: ilk([1, 2]) + 1,
          lambda: ilk(["a", "b"]) + 1,
          lambda: ilk(["a", "b"]).upper(),
          lambda: ilk([1, 2]).upper())
calisan = 0
for f in GERCEK:
    try:
        f()
    except (TypeError, AttributeError):
        calisan += 1
print(f"çağrı yeri {len(YERLER)}, gerçek ihlal "
      f"{sum(o is not g for _, o, g in YERLER)}, "
      f"çalışma zamanında hata veren {calisan}")


def esle(a, b):
    return a if a == b else b


CIFTLER = ((int, int), (int, str), (str, str))
IMZALAR = (("a: object, b: object", (object, object)),
           ("a: T, b: object", (T, object)),
           ("a: T, b: T", (T, T)))


def birlestir(imza, tipler):
    """Tip degiskenini ilk eslesmede baglar, sonra tutarliligi sinar."""
    bag = {}
    for aciklama, tip in zip(imza, tipler):
        if aciklama is T:
            if T in bag and bag[T] is not tip:
                return False
            bag[T] = tip
        elif aciklama is not object and aciklama is not tip:
            return False
    return True


print()
print(f"{'iki argümanlı imza':<24s} {'bildirilen':>11s}")
for ad, imza in IMZALAR:
    print(f"  {ad:<22s} "
          f"{sum(not birlestir(imza, c) for c in CIFTLER):11d}")
print(f"çift {len(CIFTLER)}, tipleri ayrı olan "
      f"{sum(x is not y for x, y in CIFTLER)}, "
      f"esle(1, 'a') -> {esle(1, 'a')!r} (çalışma zamanı hata vermedi)")


class Kutu(Generic[T]):
    def __init__(self, deger: T):
        self.deger = deger

    def al(self) -> T:
        return self.deger


k = Kutu[int]("dizgi")
print()
print(f"Kutu[int]('dizgi').al() -> {k.al()!r}")
print(f"type(k) is Kutu -> {type(k) is Kutu}; "
      f"Kutu[int] is Kutu[str] -> {Kutu[int] is Kutu[str]}")
print(f"isinstance(k, Kutu) -> {isinstance(k, Kutu)}")
try:
    isinstance(k, Kutu[int])
    print("isinstance(k, Kutu[int]) -> sınandı")
except TypeError:
    print("isinstance(k, Kutu[int]) -> sınanamaz")
print(f"al açıklaması: {Kutu.al.__annotations__}")

Yazi = TypeVar("Yazi", bound=str)
Sayisal = TypeVar("Sayisal", int, float)
print()
print(f"bağlı tip değişkeni sınırı: {Yazi.__bound__}")
print(f"sınırlı tip değişkeni seçenekleri: {Sayisal.__constraints__}")


def buyut(d: Yazi) -> Yazi:
    return d


print(f"buyut(7) -> {buyut(7)!r} (bağ çalışma zamanında sınanmadı)")
```

```
yazım               çıkarılan  bildirilen  doğru  kaçırılan  yanlış alarm
  açıklamasız             0/4           0      0          2             0
  list -> object          4/4           4      2          0             2
  list[int] -> int        4/4           3      2          0             1
  list[T] -> T            4/4           2      2          0             0
çağrı yeri 4, gerçek ihlal 2, çalışma zamanında hata veren 2

iki argümanlı imza        bildirilen
  a: object, b: object             0
  a: T, b: object                  0
  a: T, b: T                       1
çift 3, tipleri ayrı olan 1, esle(1, 'a') -> 'a' (çalışma zamanı hata vermedi)

Kutu[int]('dizgi').al() -> 'dizgi'
type(k) is Kutu -> True; Kutu[int] is Kutu[str] -> False
isinstance(k, Kutu) -> True
isinstance(k, Kutu[int]) -> sınanamaz
al açıklaması: {'return': ~T}

bağlı tip değişkeni sınırı: <class 'str'>
sınırlı tip değişkeni seçenekleri: (<class 'int'>, <class 'float'>)
buyut(7) -> 7 (bağ çalışma zamanında sınanmadı)
```

#### Dört Yazım, Dört Sonuç

Üst tablonun son üç sütunu birbirinden bağımsız üç kusuru sayıyor: kaçırılan ihlal, yanlış
alarm ve çıkarılamayan dönüş tipi. Dört yazımdan yalnız biri üçünü birden sıfırlıyor.

**Açıklamasız yazım** hiçbir dönüş tipi çıkarmıyor: **0/4**. Bildirim sayısı **0**,
kaçırılan **2**. Yanlış alarm da **0** — hiçbir şey söylemeyen bir imza yanılamaz da. Bu
satır bir ölçüt: aşağıdaki üç yazımın kazandırdığı şey bu satırdan uzaklaşmalarıdır.

**Gevşek yazım** dört çağrı yerinin dördünde de bir dönüş tipi çıkarıyor: **4/4**. Ama
çıkardığı tip her seferinde `object` ve hiçbir işlem `object` beklemiyor. Bildirim **4**,
doğru **2**, yanlış alarm **2**. Her çağrı yerinde bildirim yapan bir yazım, hiçbir çağrı
yerini ötekinden ayırmıyor demektir. Kaçırılan **0**'a indi, karşılığında yanlış alarm
**0**'dan **2**'ye çıktı.

**Aşırı somut yazım** üç bildirim yapıyor: **2** doğru, **1** yanlış alarm. Yanlış alarm
dizgi listesiyle yapılan geçerli çağrıdan geliyor — imza o çağrıyı argüman düzeyinde
reddediyor, oysa gövde onunla sorunsuz çalışır. Doğru bildirimlerden biri de aynı yerden
geliyor, yani doğru sonucu yanlış gerekçeyle veriyor: ihlal dönüş tarafındayken imza
argümanı suçluyor.

**Genellikli yazım** dört çağrı yerinin dördünde dönüş tipini çıkarıyor, **2** bildirim
yapıyor, ikisi de doğru, kaçırılan **0**, yanlış alarm **0**. Kaçırılanı gevşek yazım gibi
sıfırlıyor, yanlış alarmı açıklamasız yazım gibi sıfırlıyor. Tek satırda ikisi birden.

Kazancın kaynağı tek bir yerde: **bağ**. Öbür üç yazım argümanla dönüş arasında bir ilişki
kurmuyor, her birine ayrı ayrı bir tip yazıyor ya da hiç yazmıyor. Genellikli yazım tek bir
adı iki yerde kullanarak ilişkiyi bildiriyor ve çıkarım o ilişkiden çağrı yerine özel bir
yanıt üretebiliyor.

#### Aynı Adın İki Argümanda Kullanılması

Bağın gücü dönüşle sınırlı değil. İkinci tablo iki argümanlı bir işlevi ölçüyor: iki değeri
karşılaştırıp birini döndüren `esle`. Sözleşme "iki argüman **aynı** tipte olmalı"
diyecekse, bunu yazmanın tek yolu aynı tip değişkenini iki parametrede kullanmaktır.

Ölçüm bunu bir **birleştirme** adımıyla modelliyor: tip değişkeni ilk eşleşmede bir tipe
bağlanır, sonraki parametrede aynı değişken görülürse bağın tutup tutmadığına bakılır.

Üç imza, üç çift. `object` ile yazılmış imza **0** bildirim yapıyor — tipleri ayrı olan
çift var ama imza onları ayırt edecek hiçbir şey söylemiyor. `a: T, b: object` de **0**
veriyor: tip değişkeni tek yerde geçtiğinde bağ kurulmaz, çünkü bağ **iki uç ister**.
`a: T, b: T` ise **1** bildirim yapıyor ve bu, kümedeki tek gerçek uyuşmazlıktır.

Son satır kim yakaladı sorusunu yine yanıtlıyor: `esle(1, "a")` çağrısı hata vermiyor, bir
değer döndürüyor. **Çalışma zamanı bu uyuşmazlığı görmüyor**; gören tek şey imzadaki tekrar
eden addır.

#### Bu Ölçümde Kim Yakaladı

Bildirimleri yapan katman **açıklamayı okuyan taraftır** ve bu derste onun yalnız çıkarım
adımı modellendi; tam denetleyici bir sonraki dersin konusu.

Çalışma zamanı satırı ayrı bir sonuç veriyor: dört çağrı yerinde **2** hata çıkıyor, yani
gerçek ihlallerin ikisi de yakalanıyor. Bu, önceki dersin **0** sonucuyla çelişmiyor — orada gövde her tipi
kabul eden bir biçimlemeydi, burada gövdenin dışındaki işlem tipi gerçekten kullanıyor.
Fark üç noktada duruyor. Çalışma zamanı hatayı **çağrıdan sonra**, `ilk` çoktan döndükten
sonra veriyor. Yalnız **çalıştırılan** satırlarda veriyor; dört çağrı yerinden biri bir
koşulun içinde kalsaydı ve o koşul sağlanmasaydı, o ihlal görünmezdi. Ve hatayı veren şey
açıklama değil, işlemin kendisi — açıklama silinseydi aynı iki hata aynı yerlerde çıkardı.

Genellikli yazımın **2** bildirimi ise hiçbir şey çalıştırmadan, çağrı yerine bakılarak
üretiliyor. İki katmanın yakaladığı ihlaller burada aynı; **yakalama anları** ayrı.

#### Genel Sınıf Çalışma Zamanında Ne Olur

Alt bloklar genelliğin çalışma zamanındaki karşılığını ölçüyor ve sonuç önceki dersin
savını doğruluyor.

`Kutu[int]("dizgi")` bir tip ihlalidir: kutunun tam sayı taşıması bildirilmiş, içine dizgi
konmuştur. Çağrı çalışıyor, `al()` dizgiyi geri veriyor, hata yok. `type(k) is Kutu`
**doğru** — üretilen nesnenin sınıfı `Kutu`'dur; köşeli parantezdeki tip nesnenin sınıfına
işlenmez. `Kutu[int] is Kutu[str]` **yanlış**: ikisi ayrı nesnelerdir, ama ikisi de aynı
sınıfın örneklerini üretir.

`isinstance(k, Kutu)` **doğru** veriyor; `isinstance(k, Kutu[int])` ise sınanamıyor.
Önceki dersteki `list[int]` sonucunun aynısı: parametreli bir açıklama çalışma zamanı
sınamasına doğrudan verilemez. `al` yönteminin açıklaması da tip değişkeninin kendisini
taşıyor — çözülmüş bir tip değil, çözülecek yerin adını.

Son iki satır bağlı ve sınırlı tip değişkenlerini ölçüyor. Sınırlar okunabiliyor: bağlı
değişkenin üst sınırı `str`, sınırlı değişkenin seçenekleri `int` ve `float`. Ama sınırı
bozan `buyut(7)` çağrısı çalışıyor ve **7** döndürüyor. **Bağ da açıklamadır:** kaydedilir,
okunur, sınanmaz.

#### Özet

- Tip değişkeni bir tip adının yerini tutan addır; aynı adın imzada iki kez geçmesi argüman
  ile dönüş arasında bir **bağ** kurar ve genellik bu bağı taşıyan yazımdır.
- Dört yazım dört çağrı yerinde ayrışıyor: açıklamasız yazım **0** bildirim ve **2**
  kaçırılan, gevşek yazım **4** bildirim ve **2** yanlış alarm, aşırı somut yazım **3**
  bildirim ve **1** yanlış alarm veriyor.
- Genellikli yazım **2** bildirim yapıyor, ikisi de doğru; kaçırılan ve yanlış alarm
  **0**'dır. Kazancın kaynağı bağdır, tip adlarının somutluğu değil.
- Bağ iki uç ister: aynı tip değişkeni iki parametrede geçtiğinde uyuşmazlık bildirilir
  (**1**), tek parametrede geçtiğinde ya da `object` yazıldığında bildirilmez (**0**).
- Bağlı tip değişkeni bir üst sınır, sınırlı tip değişkeni sonlu bir seçenek kümesi
  bildirir; ikisinin de sınırı çalışma zamanında okunabilir ama sınanmaz.
- Genel bir sınıfın köşeli parantezdeki tipi örneğin sınıfına işlenmez: `type(k) is Kutu`
  doğrudur, `Kutu[int]` ile yapılan bir örnek sınaması ise yapılamaz.

#### Sonraki Adım

Bu derste açıklamayı okuyan tarafın yalnız bir adımı modellendi: bir çağrı yerinin dönüş
tipini çıkarmak. Gerçek bir statik denetleyici bundan fazlasını yapar — bütün çağrıları
tarar, her birinin argümanlarını imzayla karşılaştırır ve uyuşmayanları çalıştırmadan
bildirir. Sonraki ders o denetleyiciyi ders içinde modelliyor ve önceki dersin beş
çağrısına uyguluyor. Sorulan sayı şudur: dört ihlalin kaçını bulur, kaçını bulamaz — ve
bulamadıklarını neden bulamaz?

### Statik Tip Denetimi

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/statik-tip-denetimi

Ders içinde modellenen denetleyici beş çağrının iki tip ihlalini bulur ve iki alan ihlalini göremez; buna karşılık kaynaktaki üç çağrı yerinin üçünü de görür, oysa çalışma zamanı yalnız birine uğrar.

Önceki ders açıklamayı okuyan tarafın tek bir adımını modelledi: bir çağrı yerinin dönüş
tipini çıkarmak. Ölçüm, genellikli yazımın iki ihlali de yanlış alarmsız bildirebildiğini
gösterdi — ama bildirimi yapan bir program yoktu, yalnız bir çıkarım kuralı vardı.

Bu ders o programı kurar. Kaynağı ya da açıklamaları okuyup çağrıları imzayla karşılaştıran
ve uyuşmayanları **çalıştırmadan** bildiren bir katmana **statik tip denetimi (static type
checking)** denir. Sorulan sayı üç parçalıdır: bu katman ilk dersteki dört ihlalin kaçını
bulur, kaçını bulamaz ve bulamadıklarını neden bulamaz?

#### Denetleyici Bu Derste Modellenmiştir

Bir uyarı baştan: bu derste kullanılan denetleyici **ders içinde yazılmıştır**. Hazır bir
araç çalıştırılmaz ve hiçbir aracın adı geçmez. Ölçülen şey belirli bir programın davranışı
değil, **yaklaşımın kendisinin sınırıdır** — ve o sınır, denetleyicinin elindeki bilgiden
çıkar. Modellenmiş denetleyici o bilgiyi aynen kullanır: açıklamaları okur, çağrının
argümanlarına bakar, uyuşmazlıkları listeler.

Ölçüm iki denetleyici biçimi kullanıyor. Birincisi çağrıların argümanlarını doğrudan alır;
argümanlar kaynağa yazılmış sabitler olduğu için bu, kaynağı okumakla aynı bilgiyi verir.
İkincisi işi daha açık yapar: kaynak metni bir **soyut sözdizim ağacına (abstract syntax
tree)** çevirir, ağaçtaki çağrı düğümlerini gezer ve sabit argümanları imzayla
karşılaştırır. İkincisi kodu **hiç çalıştırmaz**; bu, statik sözcüğünün ölçülebilir
karşılığıdır.

#### Denetleyicinin Elindeki Bilgi

Denetleyici üç şeyi görür: imzadaki açıklamaları, çağrı yerlerini ve çağrılara yazılmış
sabit değerleri. Görmediği tek bir şey vardır ve bütün ders o şeyin sonuçlarıdır:
**değerin anlamını** görmez.

`yas: int` açıklaması "buraya bir tam sayı gelir" der. `-5` bir tam sayıdır. Denetleyici
`isinstance(-5, int)` sınamasını yapar, **doğru** yanıtını alır ve geçer. Aynı biçimde
`ad: str` açıklaması boş dizgiyi dışlamaz, çünkü boş dizgi bir dizgidir. Bu bir eksiklik
değil, imzada yazanın tam karşılığıdır: **imza tipi bildirir, alanı bildirmez.**

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

- **TP18** — Kâhin kurgunun kendisidir: beş çağrının hangisinin tip, hangisinin alan ihlali
  taşıdığı ilk derste belirlendi ve burada değiştirilmedi.
- **TP19** — Denetleyici ders içinde modellenmiştir; hiçbir dış araç çalıştırılmaz.
  Modelin kuralı tektir: açıklamadaki tiple bağlanan değerin tipi uyuşmuyorsa bildir.
- **TP20** — Alan kuralı da ders içinde modellenmiştir ve yalnız iki koşula bakar: ad boş
  mu, yaş negatif mi. Bu iki koşul kurgunun alan ihlali tanımıdır.
- **TP21** — Kapsam ölçümünde denetlenen kod bir metin sabiti olarak durur; hem soyut
  sözdizim ağacına çevrilerek denetlenir hem de çalıştırılır. İki katman **aynı metne**
  uygulanır.
- **TP22** — "Çalıştırılan çağrı yeri", koşum sırasında gerçekten uğranan çağrı sayısıdır
  ve izlenen bir sarmalayıcıyla sayılır. Koşum tek bir kiple yapılır.
- **TP23** — Soyut sözdizim ağacı denetimi yalnız **sabit** argümanları sınar; değişkenle
  verilen argüman bu modelde denetlenmez ve sayılmaz.
- **TP24** — Alan tipi ölçümünde `Yas` sınıfı tam sayıdan türetilir ve kuralı **kurulum
  anında** sınar; sınıfın örneği her yerde bir tam sayı gibi davranır.
- **TP25** — "Ham çağrıda bildirilen", kaynağa yazıldığı hâliyle beş çağrıdan kaçının
  bildirildiğidir; sayılan çağrıdır, parametre değil.
- **TP26** — "Kurulum sırasında hata veren", değerin `Yas` üzerinden kurulmaya
  çalışıldığında istisna fırlatan çağrı sayısıdır.
- **TP27** — Ortama bağlı hiçbir veri yazılmaz; satır numaraları denetlenen metin
  sabitinin kendi satırlarıdır.

#### Ölçüm

```python
"""Statik tip denetimi: modellenmis denetleyici neyi bulur, neyi goremez."""
import ast


def yas_yaz(ad: str, yas: int) -> str:
    return f"{ad}:{yas}"


CAGRILAR = (
    ("ad", 30),            # uygun
    ("ad", "otuz"),        # tip ihlali: yas dizgi
    (7, 30),               # tip ihlali: ad sayi
    ("ad", -5),            # tip uygun, alan ihlali: yas negatif
    ("", 30),              # tip uygun, alan ihlali: ad bos
)


def denetleyici(islev, cagrilar):
    """Aciklamalari okuyup cagrilari denetleyen modellenmis denetleyici.

    Yalnizca TIP uyusmazligini gorur; degerin anlamini goremez.
    """
    aciklama = [(a, t) for a, t in islev.__annotations__.items() if a != "return"]
    bulunan = []
    for i, arg in enumerate(cagrilar):
        for (ad, tip), deger in zip(aciklama, arg):
            if not isinstance(deger, tip):
                bulunan.append((i, ad))
    return bulunan


def alan_kurali(ad, yas):
    """Tipin goremedigi anlam kosullari."""
    hatalar = []
    if isinstance(ad, str) and not ad:
        hatalar.append("ad bos")
    if isinstance(yas, int) and yas < 0:
        hatalar.append("yas negatif")
    return hatalar


bulunan = denetleyici(yas_yaz, CAGRILAR)
tip_ihlali = [i for i, a in enumerate(CAGRILAR)
              if not isinstance(a[0], str) or not isinstance(a[1], int)]
alan_ihlali = [i for i, a in enumerate(CAGRILAR) if alan_kurali(*a)]

print(f"{'çağrı':>6s} {'tip ihlali':>11s} {'alan ihlali':>12s} "
      f"{'denetleyici buldu':>18s} {'çalışma zamanı':>15s}")
for i, arg in enumerate(CAGRILAR):
    print(f"{i:6d} {str(i in tip_ihlali):>11s} {str(i in alan_ihlali):>12s} "
          f"{str(any(b[0] == i for b in bulunan)):>18s} {'hata yok':>15s}")
print(f"çağrı {len(CAGRILAR)}, tip ihlali {len(tip_ihlali)}, alan ihlali "
      f"{len(alan_ihlali)}, denetleyicinin bulduğu {len({b[0] for b in bulunan})}")
print(f"denetleyicinin gösterdiği parametreler: {bulunan}")

KAYNAK = '''
def dagit(kip):
    if kip == "kisa":
        return yas_yaz("ad", 30)
    if kip == "uzun":
        return yas_yaz("ad", "otuz")
    return yas_yaz(7, 30)
'''


def statik_denetle(kaynak, islev):
    """Kaynak metni okuyup cagrilari denetleyen modellenmis denetleyici.

    Kodu calistirmaz; yalnizca soz dizim agacini gezer.
    """
    imza = [(a, t) for a, t in islev.__annotations__.items() if a != "return"]
    yerler, bulunan = [], []
    for dugum in ast.walk(ast.parse(kaynak)):
        if isinstance(dugum, ast.Call) and getattr(dugum.func, "id", "") == "yas_yaz":
            yerler.append(dugum.lineno)
            for arg, (ad, tip) in zip(dugum.args, imza):
                if isinstance(arg, ast.Constant) and not isinstance(arg.value, tip):
                    bulunan.append((dugum.lineno, ad))
    return yerler, bulunan


GECILEN = []


def izlenen(ad, yas):
    GECILEN.append((ad, yas))
    return yas_yaz(ad, yas)


ortam = {"yas_yaz": izlenen}
exec(KAYNAK, ortam)
hata = 0
try:
    ortam["dagit"]("kisa")
except Exception:
    hata += 1

yerler, statik = statik_denetle(KAYNAK, yas_yaz)
print()
print(f"kaynaktaki çağrı yeri {len(yerler)}, çalıştırılan {len(GECILEN)}, "
      f"çalışma zamanında hata {hata}")
print(f"denetleyicinin gördüğü çağrı yeri {len(yerler)}, bulduğu ihlal "
      f"{len({y for y, _ in statik})}, bulduğu yerler {statik}")

KAYNAK2 = '''
def yolla(deger):
    return yas_yaz("ad", deger)
'''
yerler2, statik2 = statik_denetle(KAYNAK2, yas_yaz)
print(f"değişkenle yazılan çağrı: görülen yer {len(yerler2)}, "
      f"sınanan argüman 0, bildirilen {len(statik2)}")


class Yas(int):
    """Alan kuralini tipe tasiyan deger nesnesi: kurulum sinar."""

    def __new__(cls, deger):
        if deger < 0:
            raise ValueError("yas negatif")
        return super().__new__(cls, deger)


def yas_yaz_alan(ad: str, yas: Yas) -> str:
    return f"{ad}:{yas}"


ham = len({b[0] for b in denetleyici(yas_yaz_alan, CAGRILAR)})
kurulan, kurulum_hatasi = [], 0
for a, y in CAGRILAR:
    try:
        kurulan.append((a, Yas(y)))
    except (ValueError, TypeError):
        kurulum_hatasi += 1
kurulu = len({b[0] for b in denetleyici(yas_yaz_alan, kurulan)})
print()
print(f"alan tipli imza: ham çağrıda bildirilen {ham}, kurulum sırasında hata veren "
      f"{kurulum_hatasi}, kurulan {len(kurulan)} çağrıda bildirilen {kurulu}")
print(f"Yas(30) bir tam sayı mı: {isinstance(Yas(30), int)}; "
      f"boş ad hâlâ görünmüyor: {denetleyici(yas_yaz_alan, [('', Yas(30))])}")
```

```
 çağrı  tip ihlali  alan ihlali  denetleyici buldu  çalışma zamanı
     0       False        False              False        hata yok
     1        True        False               True        hata yok
     2        True        False               True        hata yok
     3       False         True              False        hata yok
     4       False         True              False        hata yok
çağrı 5, tip ihlali 2, alan ihlali 2, denetleyicinin bulduğu 2
denetleyicinin gösterdiği parametreler: [(1, 'yas'), (2, 'ad')]

kaynaktaki çağrı yeri 3, çalıştırılan 1, çalışma zamanında hata 0
denetleyicinin gördüğü çağrı yeri 3, bulduğu ihlal 2, bulduğu yerler [(7, 'ad'), (6, 'yas')]
değişkenle yazılan çağrı: görülen yer 1, sınanan argüman 0, bildirilen 0

alan tipli imza: ham çağrıda bildirilen 5, kurulum sırasında hata veren 2, kurulan 3 çağrıda bildirilen 1
Yas(30) bir tam sayı mı: True; boş ad hâlâ görünmüyor: []
```

#### İki Bulunan, İki Bulunamayan

Üst tablo dersin bağlayıcı sayısını veriyor. Beş çağrının **2**'si tip ihlali, **2**'si alan
ihlali taşıyor. Denetleyicinin bulduğu çağrı sayısı **2** ve bulduğu çağrılar tam olarak tip
ihlali taşıyanlar: birinci çağrıda `yas`, ikinci çağrıda `ad`. Alan ihlali taşıyan iki
çağrıda sütun **yanlış** diyor.

**Kim yakaladı:** bu ölçümde iki ihlali **denetleyici** yakaladı, iki ihlali **hiçbir katman
yakalamadı**. Çalışma zamanı sütunu beş satırda da temiz.

Bulunamayanların nedeni tek satırda okunabilir. Üçüncü çağrıda yaş **-5**'tir ve `-5` bir
tam sayıdır; `isinstance(-5, int)` doğru döner. Dördüncü çağrıda ad boş dizgidir ve boş
dizgi bir dizgidir; `isinstance("", str)` doğru döner. Denetleyici yanlış çalışmadı —
sorulan soruyu doğru yanıtladı. Sorulmayan soru "bu değer geçerli mi" sorusuydu ve imzada
o soru yazmıyor.

Buradan kursun ölçü ekseninin en keskin cümlesi çıkıyor: **tipin doğru olması değerin
geçerli olduğu anlamına gelmez.** Denetleyiciyi daha titiz yazmak bu iki ihlali bulmaz,
çünkü sorun titizlikte değil **bilgide**: denetleyicinin elinde `yas` alanının negatif
olamayacağını söyleyen hiçbir kayıt yok.

Denetleyicinin gösterdiği parametre listesi bu bilgi sınırının biçimini de veriyor.
Bildirim yalnız çağrı numarasını değil, **hangi parametrenin** uymadığını da taşıyor:
birinci çağrıda `yas`, ikinci çağrıda `ad`. Bildirim bu kadar somut olabiliyor, çünkü
imzada o parametrenin karşısında bir tip adı yazılı. Alan kuralının karşısında yazılı bir
şey olmadığı için üretilebilecek bir bildirim de yok.

#### Kapsam Çalıştırılan Kod Değil, Yazılan Koddur

İkinci ölçüm denetleyicinin karşılığında ne verdiğini gösteriyor ve sayı çarpıcı.

Denetlenen metinde `yas_yaz` için **3** çağrı yeri var; ikisi ihlalli. Program tek bir kiple
çalıştırıldığında bu üç yerden yalnız **1**'ine uğranıyor, çünkü öbür ikisi sağlanmayan
koşulların içinde. Çalışma zamanında hata sayısı **0** — hem uğranan çağrı temiz olduğu
için, hem de önceki derste ölçüldüğü gibi açıklama zaten çağrıyı durdurmadığı için.

Soyut sözdizim ağacını gezen denetleyici ise **3** çağrı yerinin üçünü de görüyor ve **2**
ihlal bildiriyor; satır numaralarıyla birlikte, hangi parametrenin uymadığını da
söyleyerek. Kodu hiç çalıştırmadı: `ast.parse` metni bir ağaca çevirdi, `ast.walk` ağacı gezdi, hiçbir gövde
yürütülmedi.

Aradaki fark bir yüzde değil, bir **cins** farkı. Çalışma zamanı ancak uğradığı yeri
inceler; uğrama koşullara, girdiye ve o koşumdaki kipe bağlıdır. Denetleyicinin kapsamı ise
metnin kendisidir: bir kez yazılmış her çağrı yeri, hiç çalıştırılmasa bile denetlenir.
Ölçümdeki oran **3/3**'e karşı **1/3**'tür.

Oranın anlamı koşuma göre değişir. Aynı program üç ayrı kiple çalıştırılsaydı üç çağrı
yerinin üçüne de uğranır ve ikinci ile üçüncü çağrılar yine hata vermezdi — çünkü açıklama
çağrıyı durdurmaz. Yani **1/3** sayısı denetimin değil, kapsamın ölçüsüdür: hangi kod
parçasına bakıldığını söyler, bakılanın doğru bulunup bulunmadığını değil.

Bu, denetleyicinin bulamadığı iki ihlalin bedelini karşılamaz ama onunla takas edilir.
Denetleyici **daha az şey** bilir ve o azı **daha çok yerde** bilir; çalışma zamanı **daha
çok şey** bilir — elindeki gerçek değeri görür — ve onu **daha az yerde** bilir.

#### Modelin Kendi Sınırı

Bir satır bu tablonun altında duruyor ve dürüstlük gereği ayrı sayılmalı. Argümanı bir
sabit değil bir **değişken** olan çağrıda denetleyici çağrı yerini görüyor — **1** — ama
sınadığı argüman **0** ve bildirdiği ihlal **0**.

Bu, önceki bölümdeki sınırla aynı cinsten değil. Alan ihlalini bulamamak bir **bilgi**
sınırıydı: kural hiçbir yerde yazılı olmadığı için hiçbir denetleyici onu okuyamaz.
Değişkenli çağrıyı sınayamamak ise bir **model** sınırıdır: değişkenin hangi değeri
taşıdığı, ona yapılan atamalar izlenerek çıkarılabilir. Bu derste modellenen denetleyici o
izlemeyi yapmaz ve sınırı burada açıkça yazılır.

Ayrım önemli, çünkü ikisinin çaresi ayrı. Model sınırı **daha iyi bir denetleyiciyle**
kapanır. Bilgi sınırı kapanmaz — kuralı bir yere yazmadan hiçbir denetleyici onu bulamaz.
Bir sonraki bölüm o "bir yer"in ne olabileceğini deniyor.

#### Alan Kuralını Tipe Taşımak

Üçüncü ölçüm doğal bir soruyu deniyor: denetleyicinin elinde alan bilgisi yoksa, o bilgiyi
imzaya **koysak** ne olur? Ölçüm tam sayıdan türeyen bir `Yas` sınıfı kuruyor; sınıf
kuralını kurulum anında sınıyor ve örneği her yerde bir tam sayı gibi davranıyor — son satır
bunu `isinstance` ile gösteriyor.

İmza `yas: Yas` olduğunda denetleyici gerçekten değişiyor. Kaynağa yazıldığı hâliyle beş
çağrının **5**'i bildiriliyor: hiçbir sabit sayı bir `Yas` örneği değildir, temiz çağrı bile
değildir. Yani alan bilgisini imzaya koymak, **her çağrı yerini değeri kurmaya zorluyor.**

Değerler kurulduğunda tablo yerine oturuyor. Beş çağrının **2**'si kurulum sırasında hata
veriyor — negatif yaş ve tam sayı olmayan yaş, ikisi de `Yas` yapıcısında düşüyor. Geriye
kalan **3** çağrıdan **1**'i denetleyici tarafından bildiriliyor: adı sayı olan çağrı.

Ama son satır kalan boşluğu gösteriyor: boş ad hâlâ görünmüyor, çünkü `ad` için böyle bir
tip kurulmadı. Dört ihlalden üçü artık yakalanmış durumda ve yakalayanlar ayrı katmanlar —
**1**'ini denetleyici, **2**'sini kurulum.

Buradan çıkan sonuç dersin sonucudur ve bir sonrakinin başlangıcı. Alan kuralını tipe
taşımak ihlali görünür kılar, ama **görünür kıldığı yer denetleyici değil çalışma
zamanıdır**: `Yas(-5)` çağrısı bir istisna fırlatır ve bu istisna program çalışırken çıkar.
Kural yazıya değil, bir yapıcının gövdesine taşınmıştır.

#### Özet

- Statik tip denetimi, kodu çalıştırmadan okuyup çağrıları imzayla karşılaştıran bir
  katmandır; bu derste denetleyici ders içinde modellenmiştir ve hiçbir dış araç
  kullanılmamıştır.
- Denetleyici beş çağrıda **2** tip ihlalini bulur ve **2** alan ihlalini göremez. Nedeni
  bilgi eksikliğidir: `-5` bir tam sayıdır, boş dizgi bir dizgidir ve imzada alan kuralı
  yazmaz.
- Denetimin kapsamı yazılan koddur: soyut sözdizim ağacını gezen denetleyici **3** çağrı
  yerinin üçünü de görüp **2** ihlal bildirir, oysa koşum yalnız **1** çağrı yerine uğrar ve
  **0** hata verir.
- Denetleyici daha az şeyi daha çok yerde, çalışma zamanı daha çok şeyi daha az yerde bilir;
  ikisi birbirinin yerine geçmez.
- İki sınır ayrı cinstendir: değişkenli çağrıyı sınayamamak bir model sınırıdır ve daha iyi
  bir denetleyiciyle kapanır; alan kuralını görememek bir bilgi sınırıdır ve kural bir yere
  yazılmadan kapanmaz.
- Alan kuralı tipe taşındığında ham çağrıların **5**'i bildirilir, **2**'si kurulum
  sırasında hata verir, kurulan üç çağrıdan **1**'i denetleyiciye takılır — kural yazıdan
  bir yapıcının gövdesine, yani çalışma zamanına taşınmıştır.

#### Sonraki Adım

Üçüncü ölçüm kuralı çalışma zamanına taşıdı ama bunu tek bir alan için, elle yazılmış bir
sınıfla yaptı. Her alan için ayrı bir sınıf yazmak ölçeklenmez; kural sayısı arttıkça
yapıcılar birbirini tekrar eder ve kuralların nerede durduğu dağılır. Kuralları tek bir
yerde, verinin **şeması** olarak toplamak ve bu şemayı çağrı anında uygulamak da mümkündür.
Sonraki ders bu katmanı ders içinde modelliyor ve kursun son sayısını soruyor: çalışma
zamanı, denetleyici ve alan kuralı ayrı ayrı kaç ihlal yakalar — ve dört ihlalin tamamını
görmek için kaç katman gerekir?

### Çalışma Zamanı Doğrulama

Source: https://academia.sh/tr/kurslar/python-nesne-ve-tipler/calisma-zamani-dogrulama

Dört ihlali üç katman ayrı ayrı görür: çalışma zamanı 0, denetleyici 2, alan kuralı 2 yakalar ve dördü ancak iki katman birlikte kullanıldığında görünür; şema tabanlı doğrulama dördünü de yakalar ama yalnız uğradığı çağrıda.

Önceki ders alan kuralını tipe taşıdı ve kuralın bir yapıcının gövdesine, yani çalışma
zamanına düştüğünü gösterdi. Her alan için ayrı bir sınıf yazmak ölçeklenmez: kural sayısı
arttıkça yapıcılar birbirini tekrar eder ve kuralların nerede durduğu dağılır.

Kuralları tek bir yerde toplamanın adı **şema (schema)**dır: hangi alanın hangi tipte
olduğu ve hangi koşulu sağlaması gerektiği, veriden ayrı bir tanımda durur. **Çalışma
zamanı doğrulama (runtime validation)** bu tanımı çağrı anında değere uygular. Bu ders o
katmanı kurar ve kursun son sayısını sorar: dört ihlali görmek için kaç katman gerekir?

#### Şema Ders İçinde Modellenmiştir

Bir uyarı, önceki derstekinin aynısı: buradaki şema ve doğrulayıcı **ders içinde
yazılmıştır**. Hazır bir doğrulama katmanı kullanılmaz ve hiçbir aracın adı geçmez. Ölçülen
şey bir programın davranışı değil, **yaklaşımın kapsamıdır**.

Şema doğrulamanın kendisi bu kursta ilk kez geçmiyor. **Veri Modelleme ve İlişkisel Kuram**
kursu şemayı veri tarafında bütünlük kısıtlarıyla kurdu; **Web API Tasarımı** kursu da alan
düzeyi hata bildirimini istek ve yanıt gövdelerinde kurdu. **O yordamlar burada
tekrarlanmaz.** Buradaki soru dar ve ölçülebilir: **hangi katman hangi ihlali yakalar?**

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

- **TP28** — Kâhin kurgunun kendisidir: beş çağrının ikisi tip ihlali, ikisi alan ihlali
  taşır, biri temizdir. Dizi kursun ortak kurgusundan gelir ve değiştirilmez.
- **TP29** — Denetleyici ve alan kuralı önceki derslerdeki modellerin aynısıdır; denetleyici
  yalnız tipi, alan kuralı yalnız değerin anlamını görür.
- **TP30** — Şema üç şeyi tek yerde tutar: alan adı, beklenen tip ve alan koşulu. Sınama
  önce tipe, tip geçerse koşula bakar; bu sıra bir tasarım kararıdır ve iletilerde görünür.
- **TP31** — Doğrulama, işlevi saran bir katman olarak uygulanır: sınama geçmezse **çağrı
  hiç başlamaz**. Yakalama ölçüsü bu sarmalın istisna fırlatmasıdır.
- **TP32** — Çalışma zamanı sütunu sarmalsız çağrının istisna fırlatıp fırlatmadığıdır;
  gövde bir dizgi biçimlemesidir ve her tipi kabul eder.
- **TP33** — "Birlikte" sayısı iki katmanın yakaladığı çağrıların birleşimidir, toplamı
  değil.
- **TP34** — Kapsam ölçümünde beş çağrının üçünün çalıştırıldığı bir koşum varsayılır; hangi
  üçü olduğu kurguya yazılıdır ve ölçüm bunu bir olasılık olarak değil, tek bir koşum olarak
  sayar.
- **TP35** — Ortama bağlı hiçbir veri yazılmaz; bütün sayılar kurgudan ve koşumdan gelir.

#### Ölçüm

```python
"""Calisma zamani dogrulama: dort ihlali kac katman gorur."""


def yas_yaz(ad: str, yas: int) -> str:
    return f"{ad}:{yas}"


CAGRILAR = (
    ("ad", 30),            # uygun
    ("ad", "otuz"),        # tip ihlali: yas dizgi
    (7, 30),               # tip ihlali: ad sayi
    ("ad", -5),            # tip uygun, alan ihlali: yas negatif
    ("", 30),              # tip uygun, alan ihlali: ad bos
)


def denetleyici(islev, cagrilar):
    """Aciklamalari okuyan modellenmis denetleyici: yalniz tipi gorur."""
    aciklama = [(a, t) for a, t in islev.__annotations__.items() if a != "return"]
    bulunan = []
    for i, arg in enumerate(cagrilar):
        for (ad, tip), deger in zip(aciklama, arg):
            if not isinstance(deger, tip):
                bulunan.append((i, ad))
    return bulunan


def alan_kurali(ad, yas):
    """Tipin goremedigi anlam kosullari."""
    hatalar = []
    if isinstance(ad, str) and not ad:
        hatalar.append("ad bos")
    if isinstance(yas, int) and yas < 0:
        hatalar.append("yas negatif")
    return hatalar


# Sema ders icinde modellenmistir: alan adi, tip ve alan kurali tek yerde.
SEMA = (("ad", str, lambda d: bool(d), "boş"),
        ("yas", int, lambda d: d >= 0, "negatif"))


def dogrula(sema, degerler):
    """Sema tabanli dogrulama: cagri aninda hem tipi hem alani sinar."""
    hatalar = []
    for (alan, tip, kural, ileti), deger in zip(sema, degerler):
        if not isinstance(deger, tip):
            hatalar.append(f"{alan}: tip")
        elif not kural(deger):
            hatalar.append(f"{alan}: {ileti}")
    return hatalar


def korumali(sema, islev):
    """Islevi semayla saran katman: dogrulama gecmezse cagri hic baslamaz."""
    def sarmal(*arg):
        hatalar = dogrula(sema, arg)
        if hatalar:
            raise ValueError("; ".join(hatalar))
        return islev(*arg)
    return sarmal


bulunan = {b[0] for b in denetleyici(yas_yaz, CAGRILAR)}
tip_ihlali = [i for i, a in enumerate(CAGRILAR)
              if not isinstance(a[0], str) or not isinstance(a[1], int)]
alan_ihlali = [i for i, a in enumerate(CAGRILAR) if alan_kurali(*a)]
korunan = korumali(SEMA, yas_yaz)

ham_hata = 0
sema_yakalanan = set()
for i, arg in enumerate(CAGRILAR):
    try:
        yas_yaz(*arg)
    except Exception:
        ham_hata += 1
    try:
        korunan(*arg)
    except ValueError:
        sema_yakalanan.add(i)

print(f"{'çağrı':>6s} {'tip ihlali':>11s} {'alan ihlali':>12s} "
      f"{'çalışma zamanı':>15s} {'denetleyici':>12s} {'alan kuralı':>12s} "
      f"{'şema':>6s}")
for i, arg in enumerate(CAGRILAR):
    print(f"{i:6d} {str(i in tip_ihlali):>11s} {str(i in alan_ihlali):>12s} "
          f"{'hata yok':>15s} {str(i in bulunan):>12s} "
          f"{str(i in alan_ihlali):>12s} {str(i in sema_yakalanan):>6s}")
print()
print(f"çağrı {len(CAGRILAR)}, ihlalli "
      f"{len(set(tip_ihlali) | set(alan_ihlali))} | yakalayan: çalışma zamanı "
      f"{ham_hata}, denetleyici {len(bulunan)}, alan kuralı {len(alan_ihlali)}, "
      f"şema {len(sema_yakalanan)}")
print(f"denetleyici ile alan kuralı birlikte: "
      f"{len(bulunan | set(alan_ihlali))}")
print(f"şema iletileri: {[dogrula(SEMA, a) for a in CAGRILAR]}")

CALISAN = [0, 2, 4]
print()
print(f"yazılan çağrı yeri {len(CAGRILAR)}, çalıştırılan {len(CALISAN)} | "
      f"denetleyicinin gördüğü {len(CAGRILAR)}, yakaladığı {len(bulunan)} | "
      f"şemanın uğradığı {len(CALISAN)}, yakaladığı "
      f"{len(sema_yakalanan & set(CALISAN))}")
```

```
 çağrı  tip ihlali  alan ihlali  çalışma zamanı  denetleyici  alan kuralı   şema
     0       False        False        hata yok        False        False  False
     1        True        False        hata yok         True        False   True
     2        True        False        hata yok         True        False   True
     3       False         True        hata yok        False         True   True
     4       False         True        hata yok        False         True   True

çağrı 5, ihlalli 4 | yakalayan: çalışma zamanı 0, denetleyici 2, alan kuralı 2, şema 4
denetleyici ile alan kuralı birlikte: 4
şema iletileri: [[], ['yas: tip'], ['ad: tip'], ['yas: negatif'], ['ad: boş']]

yazılan çağrı yeri 5, çalıştırılan 3 | denetleyicinin gördüğü 5, yakaladığı 2 | şemanın uğradığı 3, yakaladığı 2
```

#### Üç Katman, Üç Ayrı Yakalama

Tablo dört ihlali üç sütunda dağıtıyor ve hiçbir sütun ötekinin kopyası değil.

**Çalışma zamanı 0 yakalıyor.** Beş satırda da "hata yok". Açıklama çağrıyı durdurmaz;
kursun ilk dersinde ölçülen sayı burada da aynı çıkıyor.

**Denetleyici 2 yakalıyor** ve yakaladıkları tip ihlalleridir. Alan ihlallerinde sütun
yanlış diyor, çünkü **-5** bir tam sayı, boş dizgi bir dizgidir.

**Alan kuralı 2 yakalıyor** ve yakaladıkları tam olarak denetleyicinin göremedikleridir.
İki sütun **kesişmiyor**: birinin bulduğunu öbürü bulamıyor.

Bunun doğrudan sonucu alt satırda: **denetleyici ile alan kuralı birlikte 4** yakalıyor —
ihlallerin tamamı. Toplam değil birleşim; iki katmanın kapsamı çakışmadığı için birleşim
toplama eşit çıkıyor. **Dört ihlalin tamamı ancak iki katman birlikte kullanıldığında
görünür.**

Şema sütunu ise beş çağrının **4**'ünde yakalıyor. Sebebi şemanın iki bilgiyi tek yerde
taşıması: tip ve koşul. İleti listesi bunu ayrıntısıyla veriyor — iki çağrıda `tip`, birinde
`negatif`, birinde `boş`. Yani şema yeni bir görme yeteneği getirmiyor; denetleyicinin
bildiğiyle alan kuralının bildiğini **aynı tanımda** birleştiriyor ve ikisini çağrı anında
uyguluyor.

İleti listesi şemanın bir tasarım kararını da açığa çıkarıyor. Yaşı dizgi olan çağrıda ileti
`yas: tip`, negatif olan çağrıda `yas: negatif`. Aynı alan için iki ileti birden çıkmıyor,
çünkü sınama önce tipe bakıyor ve tip geçmeden koşula geçmiyor. Bu sıra zorunlu: `d >= 0`
karşılaştırması bir dizgiyle yapılamaz. Alan koşulu **tipin doğru olduğunu varsayar**;
kural yazarken bu varsayımın nereden geldiğini bilmek, kuralın kendisini yazmak kadar
önemlidir.

Sarmal katmanın bir farkı daha var. Doğrulama geçmediğinde çağrı **hiç başlamıyor**; gövde
koşmuyor, yarım bir iş kalmıyor. Önceki derslerde ölçülen "çalışma zamanı ancak işlemi
yaparken düşer" durumu burada ortadan kalkıyor: düşme çağrının **önünde**.

Bunun bir bedeli var ve ölçümde görünüyor: sarmal, ihlalli olsun olmasın **her** çağrıda
şemayı baştan sona uyguluyor. Temiz çağrı da sınanıyor. Denetleyici ise çalışma zamanında
hiçbir şey yapmıyor; işini bir kez, koşumdan önce görüyor. İki katmanın bedeli de kapsamı
gibi ayrı cinsten: biri koşum başına bir kez, öbürü çağrı başına her seferinde.

#### Kapsam: Yazılan Kod ve Uğranan Çağrı

Son satır şemanın bedelini gösteriyor ve bu bedel kursun ölçü ekseninin son sözü.

Yazılan çağrı yeri **5**, tek bir koşumda çalıştırılan **3**. Denetleyici beşinin beşini de
görüyor ve **2** ihlal bildiriyor — kodu hiç çalıştırmadan. Şema yalnız uğradığı **3**
çağrıyı sınıyor ve o üçte **2** ihlal yakalıyor; uğramadığı iki çağrıdaki ihlaller o koşumda
görünmüyor.

Örüntü şudur: **denetleyicinin kapsamı yazılan koddur, doğrulamanın kapsamı uğranan
çağrıdır.** Şemayı daha titiz yazmak bu farkı kapatmaz, çünkü fark titizlikte değil,
katmanın ne zaman çalıştığındadır. Aynı biçimde, denetleyiciyi daha titiz yazmak alan
ihlallerini bulmasını sağlamaz — o da bilgi sınırıdır.

İki katman birbirinin yerine geçmiyor ve birbirini de doğrulamıyor: biri **her yerde az
şey**, öbürü **bazı yerlerde çok şey** biliyor.

Kapsam farkı doğrulamanın nereye konacağını da belirliyor. Şemayı her işlevin önüne koymak
sınama sayısını çağrı sayısıyla büyütür ve aynı değer aynı koşumda defalarca sınanır. Buna
karşılık verinin programa **girdiği** noktaya konan tek bir doğrulama, o noktadan sonraki
bütün çağrılar için bir güvence üretir — sınama bir kez yapılır, sonuç içeride taşınır.
Ölçümdeki şema bu yüzden alan adını da taşıyor: bir ihlal bildirildiğinde hangi alanın
sorunlu olduğu, sınamanın yapıldığı yerden uzakta da okunabilir olmalı.

Bu, önceki dersteki `Yas` sınıfının yaptığı işin ölçeklenmiş biçimidir. Orada kural tek bir
yapıcının gövdesine yazılmıştı ve her alan için ayrı bir sınıf gerekiyordu; burada kurallar
tek bir tanımda toplandı ve doğrulayıcı o tanımı okuyor. Değişen şey kuralın **ne zaman**
çalıştığı değil — ikisi de çalışma zamanıdır — kuralın **nerede yazılı olduğudur**.

#### Özet

- Şema, alan adını, beklenen tipi ve alan koşulunu tek bir tanımda toplar; çalışma zamanı
  doğrulama bu tanımı çağrı anında uygular ve doğrulama geçmezse çağrı hiç başlamaz.
- Üç katman üç ayrı şey yakalar: çalışma zamanı **0**, denetleyici **2**, alan kuralı
  **2**. İki katmanın kapsamı kesişmez, bu yüzden birleşimleri **4**'tür — dört ihlalin
  tamamı ancak birlikte görünür.
- Şema tabanlı doğrulama tek başına **4** yakalar, çünkü denetleyicinin ve alan kuralının
  bildiklerini aynı tanımda birleştirir; yeni bir görme yeteneği eklemez.
- Kapsamlar ayrıdır: denetleyici yazılan **5** çağrı yerinin beşini de görür, şema tek
  koşumda uğranan **3** çağrıyı sınar ve o üçte **2** ihlal yakalar.
- Katmanların sınırları ayrı cinstendir; denetleyicininki bilgi sınırı, doğrulamanınki
  kapsam sınırıdır ve hiçbiri ötekini titizlikle kapatamaz.

#### Kurs Kapanışı

Kurs tek bir soruyla açıldı ve on bir derste aynı soruyu iki yarıda ölçtü: **çağrıyı kim
yanıtladı, sözü kim denetledi?** Birinci yarıda yanıt bir sınıftı; ikinci yarıda bir
katman. İkisinde de ölçülen şey tasarımın ne yaptığı değil, **sorumluluğun nerede
durduğuydu**.

| ders | ölçülen | yanıtlayan ya da yakalayan |
|---|---|---|
| Sınıf ve Örnek | özniteliğin sınıfta mı örnekte mi durduğu: **1**'e karşı **3** nesne | adı ilk bulan ad alanı — önce örnek sözlüğü, sonra sınıf |
| Kalıtım ve Yöntem Çözümleme Sırası | dört yeteneğin gövdesinin nerede yazılı olduğu: **0/4**'e karşı **4/4** | çözümleme sırasında ilk gelen sınıf; bileşimde `Sarmal` |
| Kapsülleme Sözleşmeleri | dört erişim biçiminden kaçının değeri verdiği: **3**, engellenen **0** | yöntem gövdesi — ad değil |
| Özel Yöntemler | hiçbir özel yöntem yazmayan sınıfın katıldığı sözdizim: **5**, yanıtladığı: **0** | sessiz varsayılan; yazan sınıfta yöntemin kendisi |
| Özellikler ve Betimleyiciler | aynı yedi erişimin çalıştırdığı gövde: doğrudan alanda **0**, araya giren tanımda **7** | araya giren gövde; betimleyici sınıfta bir kez var edilir |
| Veri Sınıfları | kısa tanımın yazdığı özel yöntem: **4**; seçeneklerle **8**, **6** ve **10** | kısa tanımın sınıf sözlüğüne yazdığı yöntemler |
| Soyut Temel Sınıflar ve Protokoller | üç adaydan soy bağı sınamasını geçen **1**, yöntem sınamasını geçen **3** | iki sınama da; ama geçmek çağrının karşılanacağını söylemez |
| Tip İpuçları | beş çağrının **4**'ü ihlalli, çalışma zamanında hata veren **0** | hiçbir katman |
| Genellikler ve Tip Değişkenleri | dört yazımın bildirdiği ihlal: **0**, **4**, **3**, **2** | açıklamayı okuyan taraf — bağ kurulmuşsa |
| Statik Tip Denetimi | denetleyicinin bulduğu **2**, göremediği **2** | denetleyici; kapsamı yazılan kod |
| Çalışma Zamanı Doğrulama | çalışma zamanı **0**, denetleyici **2**, alan kuralı **2** | şema tabanlı doğrulama — uğradığı çağrıda **4** |

Tablonun iki yarısı aynı örüntüyü veriyor. Kalıtımda gövdenin nerede yazılı olduğunu okumak
yanıtın nereden geleceğini söylemedi; açıklamanın koda yazılmış olması da sözün
denetleneceğini söylemedi. Her iki durumda da sorunun yanıtı **yazının kendisinde değil,
onu çözen düzenekte** duruyor: birinde çözümleme sırası, öbüründe denetim katmanı.

Bütün bu ölçümlerin ortak bir varsayımı vardı ve hiç yazılmadı: **tek bir akış**. Yanıtı
veren sınıf sorulduğunda tek bir çözümleme zinciri yürüdü; ihlali yakalayan katman
sorulduğunda çağrılar sırayla, biri bitince öbürü başlayarak koştu. Bu yüzden "kim
yanıtladı" sorusunun her seferinde tek bir yanıtı oldu.

Birden çok akış olduğunda bu soru yeniden açılır. Aynı nesneye iki yerden aynı anda
uğrandığında özniteliği hangi yazmanın bıraktığı, doğrulamayı geçen bir değerin gövde
koşarken hâlâ geçerli olup olmadığı ve iki akıştan hangisinin önce davrandığı, artık
kaynağa bakarak okunamaz. Sonraki kurs **Eşzamanlılık ve Başarım** tam buradan başlıyor:
tek akışta kimin yanıtladığı bellidir; birden çok akışta bu soru yeniden sorulur ve yanıtı
bu kez bir sıralamaya bağlıdır.

