---
title: 'Özellikler ve Betimleyiciler'
source: 'https://academia.sh/tr/kurslar/python-nesne-ve-tipler/ozellikler-ve-betimleyiciler'
course: 'Nesneye Dayalı Python ve Tipler'
language: tr
updated: '2026-08-17T18:10:30+00:00'
license: 'CC BY-SA 4.0'
---

# Özellikler 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?
