Ana içeriğe geç
Kesit Python
Orta · Pythonic kod

Yineleyiciler ve üreteçler (iterators, generators): veriyi akıtarak işle

iter/next protokolünü, __iter__/__next__ ile kendi yineleyici sınıfını ve yield ile üreteç fonksiyonlarını öğrenir; büyük log akışını belleğe almadan satır satır işlersin.

Uygulamada aç, 23 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden önemli?

Ilgaz Bank'ın işlem günlüğü (log) her gün milyonlarca satır üretir. "Dosyayı oku, listeye at, sonra işle" yaklaşımı küçük dosyada çalışır; büyük dosyada belleği doldurur ve program çöker. Python'ın çözümü, veriyi tek seferde değil akıtarak işlemektir: bir satır oku, işle, bırak, sonrakine geç. Bu akışın altyapısı yineleyici (iterator) protokolüdür; en kolay yazım biçimi de üreteç (generator) fonksiyonlarıdır. Bu dersi bitirdiğinde for döngüsünün aslında ne yaptığını, dosyaların neden satır satır okunabildiğini ve kendi veri akışlarını nasıl kuracağını bileceksin.

for döngüsünün perde arkası: iter ve next

Bir listeyi for ile gezdiğinde Python iki şey yapar: önce iter(liste) ile bir yineleyici alır, sonra bitene kadar next(yineleyici) çağırır. Bittiğini anlamanın yolu StopIteration istisnasıdır.

islemler = ["EFT 500", "Havale 1200", "Kart 90"]

it = iter(islemler)
print(type(it).__name__)
print(next(it))
print(next(it))
print(next(it))
try:
    next(it)
except StopIteration:
    print("Yineleyici tükendi")

# for döngüsü tam olarak bunu yapar:
it = iter(islemler)
while True:
    try:
        satir = next(it)
    except StopIteration:
        break
    print("işle:", satir)

İki kavramı ayır: yinelenebilir (iterable), üzerinde iter() çağrılabilen her şeydir: liste, demet, sözlük, metin, dosya, range. Yineleyici (iterator), next() ile öğe veren nesnedir. Liste yinelenebilirdir ama yineleyici değildir; next(liste) hata verir. iter(liste) ise yineleyici döndürür. Yineleyiciler tek yönlüdür: geri saramazsın, tükenince biter.

next'in ikinci parametresinext(it, None) yazarsan tükendiğinde istisna yerine None döner. "İlk eşleşen öğe ya da yok" sorularında çok kullanışlıdır: next((s for s in siparisler if s["no"] == 103), None).

Kendi yineleyici sınıfın: __iter__ ve __next__

Bir sınıf iki metot tanımlarsa yineleyici olur: __iter__ kendisini döndürür, __next__ sıradaki öğeyi döndürür ya da StopIteration fırlatır. Örnek: Ilgaz Bank'ta bir hesaba günlük faiz işleyen, verilen gün kadar bakiye üreten sayaç.

class FaizAkisi:
    """Her gün bakiyeye günlük faiz ekler; gun_sayisi kadar bakiye üretir."""

    def __init__(self, bakiye, gunluk_oran, gun_sayisi):
        self.bakiye = bakiye
        self.oran = gunluk_oran
        self.kalan = gun_sayisi

    def __iter__(self):
        return self

    def __next__(self):
        if self.kalan == 0:
            raise StopIteration
        self.kalan -= 1
        self.bakiye = round(self.bakiye * (1 + self.oran), 2)
        return self.bakiye

for b in FaizAkisi(10000, 0.001, 3):
    print(b)

akis = FaizAkisi(500, 0.01, 2)
print(next(akis), next(akis), next(akis, "bitti"))

Bu sınıf tüm günlerin listesini önceden hesaplamaz; her next çağrısında bir adım ilerler. Gün sayısı bir milyon olsa da bellekte yalnız üç sayı tutar. Fakat gördüğün gibi sınıf yazmak kalabalık: durum değişkenleri, iki metot, elle istisna. Python bunun kısa yolunu sunar.

yield: üreteç fonksiyonu

İçinde yield geçen bir fonksiyon, çağrıldığında çalışmaz; bir üreteç nesnesi döndürür. Her next çağrısında kod bir sonraki yield'e kadar koşar, değeri verir ve olduğu yerde durur; yerel değişkenler korunur. Fonksiyon bitince StopIteration kendiliğinden fırlar.

def faiz_akisi(bakiye, gunluk_oran, gun_sayisi):
    for _ in range(gun_sayisi):
        bakiye = round(bakiye * (1 + gunluk_oran), 2)
        yield bakiye

g = faiz_akisi(10000, 0.001, 3)
print(type(g).__name__)
print(list(g))

# Üreteç nerede durdu? Takip edelim
def adimli():
    print("  başladı")
    yield 1
    print("  1 ile 2 arası")
    yield 2
    print("  bitiyor")

u = adimli()
print("üreteç oluştu, henüz hiçbir şey basılmadı")
print(next(u))
print(next(u))
print(next(u, "son"))

Önceki 15 satırlık sınıfın işini 4 satırlık fonksiyon yaptı. Çıktıdaki "başladı" yazısının ancak ilk next'te basıldığına dikkat et: üreteç tembeldir (lazy evaluation); istenmeden hiçbir şey hesaplamaz.

ÖzellikListe döndüren fonksiyonÜreteç fonksiyonu
Anahtar sözcükreturn listeyield öğe
Ne zaman hesaplarÇağrıda, hepsininext geldikçe, birer birer
BellekTüm sonuç kadarTek öğe ve yerel değişkenler kadar
Yeniden gezmeİstediğin kadarTek sefer; yeniden çağır
Sonsuz diziİmkânsızDoğal (while True + yield)
len, indeksVarYok

Büyük log akışını satır satır işleme

Asıl güç üreteçleri zincirlemekte ortaya çıkar. Her üreteç tek bir iş yapar ve sonucunu bir sonrakine akıtır; hiçbir aşamada tüm veri bellekte olmaz. Ilgaz Bank'ın işlem günlüğünden "HATA" satırlarını ayıklayıp tutarlarını toplayalım. Burada log'u bir metin olarak üretiyoruz; gerçekte dosya satırlarını gezersin ve dosya nesnesi de zaten tembel bir yineleyicidir.

LOG = """2024-03-01 09:12 OK EFT 500
2024-03-01 09:15 HATA Havale 1200
2024-03-01 09:20 OK Kart 90

2024-03-01 09:31 HATA EFT 2500
2024-03-01 09:40 OK Havale 300
"""

def satirlar(metin):
    """Ham satırları üret; boşları at."""
    for satir in metin.splitlines():
        satir = satir.strip()
        if satir:
            yield satir

def ayristir(satirlar):
    """Her satırı sözlüğe çevir."""
    for s in satirlar:
        tarih, saat, durum, tur, tutar = s.split()
        yield {"saat": saat, "durum": durum, "tur": tur, "tutar": int(tutar)}

def hatalar(kayitlar):
    for k in kayitlar:
        if k["durum"] == "HATA":
            yield k

boru = hatalar(ayristir(satirlar(LOG)))
print(type(boru).__name__)   # henüz tek satır bile okunmadı

toplam = 0
for k in boru:
    print(k["saat"], k["tur"], k["tutar"])
    toplam += k["tutar"]
print("Hatalı işlem toplamı:", toplam)

Üç üreteç üç küçük sorumluluk: temizle, ayrıştır, süz. Her birini ayrı ayrı test edebilir, yeniden kullanabilir, sıralarını değiştirebilirsin. Dosya büyüklüğü ne olursa olsun bellekte yalnız o anki satır bulunur. Gerçek dosyada ilk üreteci şöyle yazarsın: with open(yol) as f: for satir in f: ....

Boru hattı deseniVeri işleme kodunu "kaynak üreteci → dönüştürücü üreteçler → tüketici döngü" biçiminde kur. pandas'ta, Spark'ta, Unix borularında aynı desen vardır; burada öğrendiğin kafa yapısı oralara taşınır.

Sonsuz üreteçler ve erken durma

Üreteç tüm değerleri önceden üretmediği için sonsuz dizi tanımlamak güvenlidir; yeter ki tüketen taraf bir yerde dursun.

def islem_no(baslangic=1000):
    n = baslangic
    while True:
        yield f"TRX-{n}"
        n += 1

uretici = islem_no()
for _ in range(3):
    print(next(uretici))

# break ile dur
for no in islem_no(2000):
    if no.endswith("3"):
        print("durduk:", no)
        break
Sonsuz üreteci list() ile sarmalist(islem_no()) yazarsan program asla bitmez ve belleği tüketir. Sonsuz üreteçleri daima next, break ya da itertools.islice ile sınırla.

yield from: üreteçleri birleştir

Bir üreteç başka bir yinelenebilirin tüm öğelerini aynen aktaracaksa for x in kaynak: yield x yerine yield from kaynak yazarsın. Hem kısadır hem iç içe yapıları (ağaç, kategori ağacı) gezmek için doğal okunur.

def ist_siparisleri():
    yield from ["IST-1", "IST-2"]

def ank_siparisleri():
    yield "ANK-1"

def tum_siparisler():
    yield from ist_siparisleri()
    yield from ank_siparisleri()
    yield "MANUEL-1"

print(list(tum_siparisler()))

# Kategori ağacını düzleştir
kategoriler = {
    "ad": "Elektronik",
    "alt": [
        {"ad": "Telefon", "alt": []},
        {"ad": "Bilgisayar", "alt": [{"ad": "Dizüstü", "alt": []}]},
    ],
}

def adlar(kat):
    yield kat["ad"]
    for alt in kat["alt"]:
        yield from adlar(alt)

print(list(adlar(kategoriler)))

adlar fonksiyonu kendini çağırıyor (özyineleme (recursion)) ve her alt dalın ürettiklerini yield from ile yukarı aktarıyor. Aynı şeyi iç içe döngülerle yazmak hem uzun hem hataya açıktır.

itertools'a köprü

Üreteçlerle yaptığın birçok işin hazır, C ile yazılmış ve tembel çalışan sürümü itertools modülündedir. Birkaç tanıdık:

import itertools

def islem_no(n=1):
    while True:
        yield f"TRX-{n}"
        n += 1

# Sonsuz üreteçten güvenle ilk 3'ü al
print(list(itertools.islice(islem_no(), 3)))

# Koşul bozulana kadar al
tutarlar = [100, 250, 400, 90, 800]
print(list(itertools.takewhile(lambda t: t < 300, tutarlar)))

# Birden fazla kaynağı tek akışta gez
ist = ["IST-1", "IST-2"]
ank = ["ANK-1"]
print(list(itertools.chain(ist, ank)))

# Koşarak toplam (running total)
print(list(itertools.accumulate([100, 250, 400])))

Bunların hepsi üreteç döndürür; yani zincirine sorunsuz eklenirler. Ayrıntılı tur için ileride "collections/itertools" dersi var; bu derste bilmen gereken şu: kendi üretecini yazmadan önce itertools'ta hazırı var mı diye bak.

Derinleş

Derinleş: akışlarla çalışmanın inceliği

iter/next, kendi yineleyici sınıfın, yield, yield from ve sonsuz üreteçleri gördün. Bu bölümde gerçek veri işlerinde en çok işine yarayacak kalıpları ekliyoruz: hangi nesnelerin "tek kullanımlık" olduğu, next'in varsayılan değeri ve iter'in az bilinen iki argümanlı biçimi, üreteçlerden boru hattı (pipeline) kurmak, veriyi parçalar (batch) hâlinde işlemek, tekrar tekrar gezilebilen sınıflar ve üretecin yaşam döngüsü. itertools araçlarının çoğu collections ve itertools dersinde ayrıca ele alınıyor.

Yinelenebilir mi, yineleyici mi? Kim tek kullanımlık?

Yinelenebilir (iterable), iter() verildiğinde her seferinde yeni bir yineleyici üreten nesnedir: liste, demet, sözlük, range. Yineleyici (iterator) ise kendi konumunu tutar ve bir kez tükenir. Python'un birçok yerleşik fonksiyonu yineleyici döndürür; bu yüzden ikinci kullanımda boş gelirler.

NesneTürİkinci for döngüsünde
list, tuple, dict, stryinelenebilirBaştan gezer
range(10)yinelenebilir (tembel)Baştan gezer
zip, map, filter, enumerate, reversedyineleyiciBoş
Üreteç fonksiyonu/ifadesiyineleyiciBoş
Açık dosya nesnesiyineleyiciBoş (imleç sonda)
r = range(3)
print(list(r), list(r))              # range tekrar gezilir

eslesme = zip(["Ayşe", "Can"], [92, 61])
print(list(eslesme), list(eslesme))  # zip ikinci kez boş

it = iter([10, 20])
print(iter(it) is it)                # yineleyicinin iter'i kendisidir
print(next(it), next(it), next(it, "bitti"))   # varsayılan: StopIteration yok
Basit kuraliter(x) is x True ise x tek kullanımlık bir yineleyicidir. Bir fonksiyona veri verip içeride iki kez gezecek misin? Ya liste ver ya da fonksiyonun başında veri = list(veri) yaz.

iter(fonksiyon, bitiş_değeri)

iter'e iki argüman verirsen anlamı değişir: birinci argüman argümansız bir fonksiyondur, Python onu tekrar tekrar çağırır ve dönen değer bitiş değerine (sentinel) eşit olunca durur. Dosyayı sabit boyutlu parçalarla okumanın klasik yoludur.

import io
from functools import partial

akis = io.StringIO("TRX1TRX2TRX3TR")         # dosya gibi davranan metin
for parca in iter(partial(akis.read, 4), ""):  # read(4) "" dönünce dur
    print(repr(parca))

kuyruk = [7, 3, 0, 9]                          # 0 gelince dur
print(list(iter(kuyruk.pop, 9)))               # sondan: 9 hemen bitiş!
print(list(iter(lambda: kuyruk.pop(0), 0)))    # baştan: 7, 3, sonra 0

Üreteç boru hattı: her aşama bir fonksiyon

Büyük bir log dosyasını işlerken "oku → ayrıştır → süz → topla" aşamalarını ayrı üreteçler olarak yazıp birbirine bağlarsın. Hiçbir aşama listenin tamamını tutmaz; her satır boru hattından tek tek akar. Aşağıdaki çıktıda aşamaların iç içe (sırayla değil) çalıştığına dikkat et.

LOG = """2026-10-27 09:01 TR01 havale 1500
bozuk satır
2026-10-27 09:05 TR02 eft 25000
2026-10-27 09:07 TR01 havale 300"""

def satirlar(metin):
    for s in metin.splitlines():
        print("  okundu:", s[:16])
        yield s

def ayristir(satir_akisi):
    for s in satir_akisi:
        parca = s.split()
        if len(parca) == 5:
            yield {"hesap": parca[2], "tur": parca[3], "tutar": float(parca[4])}

def buyukler(kayitlar, esik):
    return (k for k in kayitlar if k["tutar"] >= esik)

hat = buyukler(ayristir(satirlar(LOG)), 1000)
print("Boru hattı kuruldu, henüz hiçbir şey okunmadı")
for k in hat:
    print("SONUÇ:", k)

Parça parça (batch) işlemek

Veritabanına 100'erli gruplar hâlinde yazmak, API'ye 50'şer kayıt göndermek gibi işler için akışı sabit boyutlu parçalara bölersin. Python 3.12 bunun için itertools.batched getirdi; eski sürümlerde islice ile aynısını yazarsın.

from itertools import islice, batched      # batched: Python 3.12+

def parcalar(yinelenebilir, n):
    it = iter(yinelenebilir)               # tek bir yineleyici şart
    while parca := list(islice(it, n)):
        yield parca

print(list(parcalar(range(1, 8), 3)))
print(list(batched(range(1, 8), 3)))      # demetler döndürür
iter(...) satırını unutmaislice(liste, n) her çağrıda listenin başından başlar; sonsuz döngüye girersin. Konumu tutan tek bir yineleyici oluşturup islice'a hep onu vermelisin.

Tekrar gezilebilen sınıf: __iter__ içinde yield

Kendi yineleyici sınıfında __iter__ self döndürüyorsa nesne tek kullanımlıktır. Çoğu zaman istediğin ise "liste gibi davranan" bir koleksiyondur: her for döngüsünde baştan başlamalı. Çözüm: __next__ yazma, __iter__'i üreteç fonksiyonu yap. Her çağrı yeni bir üreteç döndürür.

class SiparisDefteri:
    def __init__(self, siparisler):
        self._siparisler = list(siparisler)

    def __iter__(self):                    # her çağrıda yeni üreteç
        for s in self._siparisler:
            if s["durum"] != "iptal":
                yield s

defter = SiparisDefteri([{"no": 1, "durum": "ok"}, {"no": 2, "durum": "iptal"}, {"no": 3, "durum": "ok"}])
print([s["no"] for s in defter])
print([s["no"] for s in defter])          # yine baştan
print(sum(1 for _ in defter), "geçerli sipariş")

Üretecin yaşam döngüsü: return, close ve temizlik

Üreteç fonksiyonunda return deger yazabilirsin: akış biter ve değer StopIteration.value içinde taşınır; yield from bu değeri sonuç olarak verir. Tüketici akışı yarıda bırakırsa (break) üreteç çöp toplanırken ya da close() çağrılınca içerideki finally bloğu çalışır; dosya, bağlantı gibi kaynakları orada kapatırsın.

def kayit_oku():
    print("bağlantı açıldı")
    try:
        for i in range(1, 100):
            yield i
    finally:
        print("bağlantı kapandı")

g = kayit_oku()
for x in g:
    if x == 3:
        break
g.close()                                  # finally hemen çalışır

def alt():
    yield "a"
    return 42

def ust():
    sonuc = yield from alt()
    yield f"alt üreteç {sonuc} döndürdü"

print(list(ust()))
Gerçek işteVeri mühendisliğinde "akış" (streaming) düşüncesi her yerdedir: gigabaytlık CSV'yi satır satır okuyup 1000'erlik paketlerle veritabanına yazan bir ETL betiği, birkaç megabayt bellekle çalışır. Aynı işi önce readlines() ile listeye alıp yapan betik ise sunucuda bellek yetmeyince çöker. pandas'ın read_csv(chunksize=...), veritabanı imleçlerinin fetchmany'si ve bulut SDK'larının sayfalama (pagination) yardımcıları bu bölümdeki kalıpların kütüphane hâlidir.
Uzmanlaş

Üreteçle iki yönlü iletişim

yield bir ifadedir, değer de alabilir: alinan = yield verilen yazdığında dışarıdan g.send(deger) ile üretece veri gönderebilirsin. Bu özellik, durumu koruyan işlemci (coroutine) yazmayı sağlar; örneğin koşan ortalama hesaplayan, her gönderilen tutarda güncel ortalamayı veren bir üreteç. g.close() üreteci düzgünce kapatır, içindeki finally bloklarını çalıştırır. Python'ın asyncio altyapısı tarihsel olarak bu mekanizmanın üstüne kuruldu; İleri seviyedeki async dersinde "await" gördüğünde "yield'in bir kuzeni" diye düşünebilirsin.

İş hayatında nasıl görünür?

Veri mühendisliği işlerinde "dosyayı bir seferde okuma" kuralı neredeyse kutsaldır. Bir ETL betiği 20 GB'lık CSV'yi satır satır gezer, her satırı dönüştürür ve hedefe parça parça yazar; bu dersin boru hattı deseni birebir oradadır. API sayfalama (pagination) kodu da tipik bir üreteçtir: "sayfa çek, kayıtları yield et, sonraki sayfa varsa devam et"; tüketici tarafı sayfa kavramını hiç görmez. Kod incelemesinde (code review) "bu fonksiyon liste döndürmek yerine yield etmeli" notu, bellek sorununu daha oluşmadan önleyen klasik bir yorumdur.

Alıştırmalara geç (23)