---
title: 'Soyut Temel Sınıflar ve Protokoller'
source: 'https://academia.sh/tr/kurslar/python-nesne-ve-tipler/soyut-temel-siniflar-ve-protokoller'
course: 'Nesneye Dayalı Python ve Tipler'
language: tr
updated: '2026-08-17T18:10:29+00:00'
license: 'CC BY-SA 4.0'
---

# Soyut Temel Sınıflar 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?
