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(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.
| Özellik | Liste döndüren fonksiyon | Üreteç fonksiyonu |
|---|---|---|
| Anahtar sözcük | return liste | yield öğe |
| Ne zaman hesaplar | Çağrıda, hepsini | next geldikçe, birer birer |
| Bellek | Tüm sonuç kadar | Tek öğe ve yerel değişkenler kadar |
| Yeniden gezme | İstediğin kadar | Tek sefer; yeniden çağır |
| Sonsuz dizi | İmkânsız | Doğal (while True + yield) |
| len, indeks | Var | Yok |
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: ....
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
list(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ş: 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.
| Nesne | Tür | İkinci for döngüsünde |
|---|---|---|
list, tuple, dict, str | yinelenebilir | Baştan gezer |
range(10) | yinelenebilir (tembel) | Baştan gezer |
zip, map, filter, enumerate, reversed | yineleyici | Boş |
| Üreteç fonksiyonu/ifadesi | yineleyici | Boş |
| Açık dosya nesnesi | yineleyici | Boş (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
iter(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
islice(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()))
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.Ü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.