---
title: Döngüler
source: 'https://academia.sh/tr/kurslar/python-temelleri/donguler'
course: 'Python Temelleri'
language: tr
updated: '2026-08-17T18:10:29+00:00'
license: 'CC BY-SA 4.0'
---

# Döngüler

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

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

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

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

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

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

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

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

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

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

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

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

## Ölçüm

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

KAYIT = []


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


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

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

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

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

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

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


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

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

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


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


TUR = []


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


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


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


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


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


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


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

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

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

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

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


DIS = []


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


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


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


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


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

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

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

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

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

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

## Bir Fazla Çağrı

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

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

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

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

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

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

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

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

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

## İkinci Protokol

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

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

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

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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