Python Generators ve Iterators: Bellek Verimli Programlamanın Sırrı
Python Generators ve Iterators: Bellek Verimli Programlamanın Sırrı
Diyelim ki bir kütüphanedesiniz ve 10 milyon kitabın listesini almanız gerekiyor. İki seçeneğiniz var: ya tüm kitapları dev bir kamyona yükleyip evinize getirirsiniz, ya da kütüphaneden tek tek alıp okuduğunuz kitabı geri verirsiniz. İlk yöntemde evinize sığmayabilir — belki garajı da doldurursunuz, belki komşunun bahçesini de. İkinci yöntemde ise elinizde her an sadece bir kitap var ve bellek problemi diye bir şey yok.
İşte Python'daki generators ve iterators tam olarak bu ikinci yöntemi temsil ediyor. Verileri bellekte biriktirmek yerine, ihtiyaç duyulduğunda teker teker üretiyorlar. Bu özellikle büyük veri setleri, dosya okuma, API pagination ve sonsuz seriler gibi senaryolarda hayat kurtarıcı.
Peki neden bu kadar önemli? Çünkü gerçek dünya projelerinde karşılaştığınız veri miktarları, RAM'inize sığmayabilir. 5 GB'lık bir log dosyasını okumak, milyonlarca veritabanı kaydını işlemek, sürekli akan sensör verilerini analiz etmek… Bunların hepsinde klasik liste yaklaşımı ya programınızı patlatır ya da yavaşlatır. Generators bu sorunu kökünden çözer: lazy evaluation (tembel değerlendirme) prensibiyle verileri yalnızca istendiğinde üretir, bellekte tutmaz.
Bu yazıda Python'ın iteration protokolünü sıfırdan anlayacak, generator'ların nasıl çalıştığını perde arkasından görecek, gerçek projelerde nasıl kullanıldığını öğrenecek ve yaygın hatalardan nasıl kaçınacağınızı bileceksiniz.
Iteration Protokolü: Her Şeyin Temeli
Python'da bir for döngüsü yazdığınızda arka planda neler olduğunu hiç merak ettiniz mi? for item in my_list dediğinizde, Python aslında bir protokol çalıştırıyor — Iteration Protocol (yineleme protokolü). Bu protokolü anlamadan generator'ları tam olarak kavramak zor.
Protokol iki temel metoda dayanır:
__iter__()— Nesnenin kendisini (veya bir iterator nesnesini) döndürür. "Ben üzerinde gezinilebilir bir şeyim" demektir.__next__()— Sıradaki elemanı döndürür. Eleman kalmadığındaStopIterationexception'ı fırlatır.
Bir for döngüsü yazdığınızda Python şunları yapar:
Nesne üzerinde
iter()çağırır → bir iterator nesnesi alırBu iterator üzerinde
next()çağırır → sıradaki elemanı alırStopIterationgelene kadar 2. adımı tekrarlar
Bunu kanıtlayalım:
# for döngüsünün perde arkası
sayilar = [10, 20, 30]
# Bu for döngüsü...
for sayi in sayilar:
print(sayi)
# ...aslında şunu yapıyor:
iterator = iter(sayilar) # __iter__() çağrılır
while True:
try:
sayi = next(iterator) # __next__() çağrılır
print(sayi)
except StopIteration:
break # Eleman bitti, döngü sona erer
# Çıktı (her ikisi için aynı):
# 10
# 20
# 30Şimdi önemli bir ayrım: iterable ve iterator farklı kavramlar.
Iterable (gezinilebilir):
__iter__()metodu olan her nesne. Listeler, tuple'lar, string'ler, dictionary'ler hep iterable'dır. Bunlar üzerinde birden fazla kezfordöngüsü kurabilirsiniz.Iterator (yineleyici): Hem
__iter__()hem__next__()metodu olan nesne. Bir kez tüketilir, ikinci kez kullanamazsınız.
🎯 Analoji: Iterable bir kitaptır — istediğiniz kadar baştan okuyabilirsiniz. Iterator ise kitabın sayfalarını yırtan bir okuyucu — bir kez okudu mu, sayfalar gitti.
Kendi iterator'ımızı yazarak bunu somutlaştıralım:
class GeriSayim:
"""Verilen sayıdan 1'e kadar geri sayan iterator"""
def __init__(self, baslangic):
self.baslangic = baslangic
self.mevcut = baslangic
def __iter__(self):
# Iterator'ın kendisini döndür
return self
def __next__(self):
if self.mevcut <= 0:
raise StopIteration # Eleman kalmadı
deger = self.mevcut
self.mevcut -= 1
return deger
# Kullanım
for sayi in GeriSayim(5):
print(sayi, end=" ")
# Çıktı: 5 4 3 2 1Bu kod çalışıyor ama dikkat edin: __init__, __iter__, __next__, durum yönetimi, StopIteration fırlatma… Basit bir geri sayım için bile epey kod yazdık. İşte tam bu noktada generator'lar sahneye çıkıyor.
Generators: Iterator Yazmayı Kolaylaştıran Sihir
Generator, bir iterator'ı tek satırlık bir fonksiyonla oluşturmanızı sağlayan Python özelliği. Yukarıda yazdığımız GeriSayim sınıfının tamamını şu şekilde yazabilirsiniz:
def geri_sayim(baslangic):
"""Verilen sayıdan 1'e kadar geri sayan generator"""
mevcut = baslangic
while mevcut > 0:
yield mevcut # Değeri üret ve dur
mevcut -= 1
# Kullanım — tıpatıp aynı
for sayi in geri_sayim(5):
print(sayi, end=" ")
# Çıktı: 5 4 3 2 1yield anahtar kelimesi buradaki sihirli kelime. Bir fonksiyonda yield gördüğünüz an, o fonksiyon artık normal bir fonksiyon değil — bir generator fonksiyondur. Çağırdığınızda kodu çalıştırmaz, bir generator nesnesi döndürür. Bu nesne üzerinde next() çağırdığınızda kod yield'a kadar çalışır, değeri döndürür ve durur. Bir sonraki next() çağrısında kaldığı yerden devam eder.
Bunu adım adım görelim:
def basit_generator():
print("Birinci yield öncesi")
yield 1
print("İkinci yield öncesi")
yield 2
print("Üçüncü yield öncesi")
yield 3
print("Fonksiyon sonu")
gen = basit_generator()
print(type(gen))
# Çıktı: <class 'generator'>
print(next(gen))
# Çıktı:
# Birinci yield öncesi
# 1
print(next(gen))
# Çıktı:
# İkinci yield öncesi
# 2
print(next(gen))
# Çıktı:
# Üçüncü yield öncesi
# 3
# Bir kez daha çağırırsak:
# next(gen) → StopIteration fırlatır
# "Fonksiyon sonu" yazdırılır ama değer döndürülmezGördüğünüz gibi yield bir duraklama noktası. Fonksiyon o noktada donuyor, tüm yerel değişkenleri (local variables) bellekte korunuyor, ve next() çağrıldığında tam kaldığı yerden devam ediyor. return'den temel farkı bu: return fonksiyonu tamamen bitirir ve her şeyi siler, yield ise askıya alır.
💡 İpucu: Generator fonksiyonun içinde
returnda kullanabilirsiniz — bu durumda generator hemenStopIterationfırlatır ve sonlanır.returnile bir değer döndürürseniz, bu değerStopIterationexception'ınınvalueattribute'unda saklanır amafordöngüsü bu değeri görmez.
Bellek Farkı: Liste vs Generator
Generator'ların gerçek gücü bellek yönetiminde ortaya çıkıyor. Bunu somut bir örnekle ölçelim:
import sys
# Liste yaklaşımı — tüm elemanlar bellekte
liste = [x * x for x in range(1_000_000)]
print(f"Liste boyutu: {sys.getsizeof(liste):,} bytes")
# Çıktı: Liste boyutu: 8,448,728 bytes (~8 MB)
# Generator yaklaşımı — tembel değerlendirme
generator = (x * x for x in range(1_000_000))
print(f"Generator boyutu: {sys.getsizeof(generator):,} bytes")
# Çıktı: Generator boyutu: 200 bytes
# İkisi de aynı sonucu verir
print(sum([x * x for x in range(1_000_000)])) # Liste ile
print(sum(x * x for x in range(1_000_000))) # Generator ile
# Her ikisi de: 3333328333335000008 MB'a karşı 200 byte! 42.000 kat fark var. Generator bellekte sadece mevcut durumunu (hangi adımda olduğunu) tutuyor. Liste ise 1 milyon tam sayının hepsini RAM'de saklıyor.
Şimdi düşünün: 10 milyon, 100 milyon, 1 milyar eleman olsa ne olur? Liste yaklaşımında RAM tükenir ve MemoryError alırsınız. Generator ile hiç sorun olmaz — çünkü her an bellekte sadece bir eleman var.
⚠️ Dikkat: Generator expression (üretici ifade) ile list comprehension (liste üretici) sözdizimi neredeyse aynıdır. Tek fark parantez tipi: köşeli parantez
[...]liste üretir, normal parantez(...)generator üretir. Bu küçük fark bellek kullanımında devasa sonuçlar yaratır.
Generator Expressions: Tek Satırlık Güç
Generator fonksiyon yazmak yerine, basit durumlar için generator expression kullanabilirsiniz. List comprehension'ın tembel (lazy) versiyonu olarak düşünebilirsiniz:
# List comprehension — hemen hesaplar, belleğe yazar
kareler_liste = [x ** 2 for x in range(10)]
# Generator expression — hesaplamayı erteler
kareler_gen = (x ** 2 for x in range(10))
# İkisini de döngüde kullanabilirsiniz
for kare in kareler_gen:
print(kare, end=" ")
# Çıktı: 0 1 4 9 16 25 36 49 64 81
# Fonksiyonlara doğrudan geçirebilirsiniz
toplam = sum(x ** 2 for x in range(10))
print(toplam) # 285
en_buyuk = max(x ** 2 for x in range(10))
print(en_buyuk) # 81
# Filtreleme ile birlikte
cift_kareler = sum(x ** 2 for x in range(20) if x % 2 == 0)
print(cift_kareler) # 1140sum(), max(), min(), any(), all() gibi fonksiyonlara generator expression geçirmek özellikle güzel çalışır. Çünkü bu fonksiyonlar zaten elemanları teker teker tüketir — listeye ihtiyaçları yok.
yield from: Alt Generator'ları Bağlama
Python 3.3 ile gelen yield from ifadesi, bir generator'dan başka bir iterable veya generator'ı delege etmenizi sağlar. İç içe generator'larla çalışırken çok işe yarar.
def sayilar():
yield from range(3) # 0, 1, 2 üretir
def harfler():
yield from "abc" # 'a', 'b', 'c' üretir
def birlestir():
yield from sayilar() # Önce sayılar
yield from harfler() # Sonra harfler
yield from [100, 200] # Sonra liste
for eleman in birlestir():
print(eleman, end=" ")
# Çıktı: 0 1 2 a b c 100 200yield from olmadan aynı şeyi yapmak için iç döngü yazmanız gerekirdi:
# yield from kullanmadan — daha uzun, daha karmaşık
def birlestir_eski():
for sayi in sayilar():
yield sayi
for harf in harfler():
yield harf
for eleman in [100, 200]:
yield elemanyield from özellikle recursive (özyinelemeli) generator'larda parlıyor. Bir dizin ağacını (directory tree) dolaşan generator düşünün:
import os
def dosya_tara(dizin):
"""Verilen dizin ve alt dizinlerdeki tüm dosyaları üretir"""
for girdi in os.scandir(dizin):
if girdi.is_file():
yield girdi.path
elif girdi.is_dir():
yield from dosya_tara(girdi.path) # Alt dizine dal
# Kullanım — milyonlarca dosya olsa bile bellek sabit kalır
for dosya in dosya_tara("/var/log"):
if dosya.endswith(".log"):
print(dosya)Bu generator ne kadar derin bir dizin yapısı olursa olsun, bellekte sadece mevcut dosya yolunu tutar. Tüm dosya listesini bir listeye yüklemez.
Gerçek Dünya Senaryoları
Generators'ın teorisini anladık. Şimdi gerçek projelerde nerede kullanılır, onu görelim.
Senaryo 1: Büyük Dosya Okuma
Bir log dosyasını satır satır işlemek, generator'ların en klasik kullanım alanıdır:
def dosya_satir_oku(dosya_yolu, kodlama="utf-8"):
"""Büyük dosyaları bellek dostu şekilde satır satır okur"""
with open(dosya_yolu, "r", encoding=kodlama) as dosya:
for satir in dosya:
yield satir.strip()
def hata_satirlari_bul(dosya_yolu):
"""Log dosyasından sadece ERROR satırlarını filtreler"""
for satir in dosya_satir_oku(dosya_yolu):
if "ERROR" in satir:
yield satir
# 5 GB'lık bir log dosyasını bile işleyebilir
# Bellekte sadece 1 satır bulunur
for hata in hata_satirlari_bul("/var/log/app.log"):
print(hata)Bu kodda open() fonksiyonunun kendisi de aslında bir iterator döndürür — dosyayı satır satır okur. Biz üzerine kendi generator katmanımızı ekleyerek bir pipeline (boru hattı) oluşturuyoruz.
Senaryo 2: API Pagination
Bir API'den sayfalanmış (paginated) veri çekerken generator kullanmak, hem kodu temizler hem de bellek kullanımını düşürür:
import requests
def api_sayfa_getir(base_url, sayfa_boyutu=100):
"""API'den tüm sayfaları tembel olarak çeker"""
sayfa = 1
while True:
yanit = requests.get(
base_url,
params={"page": sayfa, "per_page": sayfa_boyutu}
)
veriler = yanit.json()
if not veriler:
break # Veri kalmadı, generator biter
for kayit in veriler:
yield kayit
sayfa += 1
# Kullanım — 100.000 kayıt olsa bile bellekte sadece 1 kayıt
for urun in api_sayfa_getir("https://api.example.com/products"):
print(f"Ürün: {urun['name']} - Fiyat: {urun['price']}")
# İstediğiniz zaman durabilirsiniz — gereksiz API çağrısı yapılmaz
if urun["price"] > 1000:
print("Pahalı ürün bulundu, duruyorum.")
breakBuradaki break çok önemli. Generator tembel olduğu için, ihtiyacınız olan veriyi bulduğunuzda döngüden çıktığınızda geriye kalan sayfalar için API çağrısı yapılmaz. Liste yaklaşımında ise tüm sayfaları çekip bitirmeniz gerekirdi.
Senaryo 3: Veri İşleme Pipeline'ı
Generator'ları birbirine bağlayarak güçlü veri işleme boru hatları oluşturabilirsiniz. Unix pipe'larına benzer bir yaklaşım:
def satir_oku(dosya_yolu):
"""1. aşama: dosyadan satır oku"""
with open(dosya_yolu) as f:
for satir in f:
yield satir.strip()
def json_parse(satirlar):
"""2. aşama: her satırı JSON olarak parse et"""
import json
for satir in satirlar:
if satir:
yield json.loads(satir)
def filtrele(kayitlar, alan, deger):
"""3. aşama: belirli bir alana göre filtrele"""
for kayit in kayitlar:
if kayit.get(alan) == deger:
yield kayit
def donustur(kayitlar):
"""4. aşama: veriyi dönüştür"""
for kayit in kayitlar:
yield {
"ad": kayit["name"].upper(),
"toplam": kayit["price"] * kayit["quantity"]
}
# Pipeline'ı birleştir
satirlar = satir_oku("orders.jsonl")
kayitlar = json_parse(satirlar)
aktif_kayitlar = filtrele(kayitlar, "status", "active")
sonuclar = donustur(aktif_kayitlar)
# Tüm pipeline tembel — ilk next() çağrısına kadar hiçbir şey çalışmaz
for sonuc in sonuclar:
print(sonuc)Bu pipeline'ın güzelliği şu: dosya ne kadar büyük olursa olsun bellekte her an yalnızca bir kayıt var. Her aşama önceki aşamadan bir eleman alır, işler ve sonraki aşamaya geçirir. Tıpkı fabrikadaki üretim bandı gibi — tüm ürünleri depolamıyorsunuz, bant üzerinde birer birer ilerliyorlar.
Sonsuz Seriler: Bitmeyen Generator'lar
Generator'ların liste üzerindeki en belirgin avantajı sonsuz seriler üretebilmesidir. Bir listeye sonsuz eleman koyamazsınız ama bir generator sonsuza kadar değer üretebilir:
def fibonacci():
"""Sonsuz Fibonacci serisi üretir"""
a, b = 0, 1
while True: # Sonsuz döngü — ama sorun yok!
yield a
a, b = b, a + b
# İlk 15 Fibonacci sayısı
from itertools import islice
for sayi in islice(fibonacci(), 15):
print(sayi, end=" ")
# Çıktı: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
# 1000'den büyük ilk Fibonacci sayısı
for sayi in fibonacci():
if sayi > 1000:
print(f"\n1000'den büyük ilk Fibonacci: {sayi}")
break
# Çıktı: 1000'den büyük ilk Fibonacci: 1597itertools.islice() fonksiyonu generator'larla çalışırken çok kullanışlı. Normal dilim (slice) operatörü [:] generator'larda çalışmaz çünkü generator indekslenebilir (indexable) değildir. islice() bu sorunu çözer.
⚠️ Dikkat: Sonsuz bir generator üzerinde
list(),len(),sum()gibi tüm elemanları tüketmeye çalışan fonksiyonları çağırmayın. Program sonsuza kadar çalışır veya bellek tükenir. Her zamanislice(),breakveyatakewhile()ile sınırlayın.
Generator'lara Veri Göndermek: send() Metodu
Generator'lar sadece veri üretmez, dışarıdan veri de alabilir. send() metodu ile generator'a çalışma anında veri gönderebilirsiniz:
def hareketli_ortalama():
"""Gelen sayıların hareketli ortalamasını hesaplar"""
toplam = 0
adet = 0
ortalama = None
while True:
deger = yield ortalama # Dışarıdan değer al, ortalamayı döndür
if deger is not None:
toplam += deger
adet += 1
ortalama = toplam / adet
# Kullanım
ort = hareketli_ortalama()
next(ort) # Generator'ı başlat (ilk yield'a kadar çalıştır)
print(ort.send(10)) # Çıktı: 10.0
print(ort.send(20)) # Çıktı: 15.0
print(ort.send(30)) # Çıktı: 20.0
print(ort.send(40)) # Çıktı: 25.0send() kullandığınızda yield ifadesi iki yönlü çalışır: hem değer döndürür hem de değer alır. deger = yield ortalama satırında, ortalama dışarıya döner, send() ile gönderilen değer ise deger'e atanır.
💡 İpucu:
send()kullanmadan önce generator'ınext()ile başlatmanız gerekir. Çünkü generator ilkyield'a kadar ilerlemeli kisend()ile gönderdiğiniz değeri alabilsin. Bu adımı atlarsanızTypeError: can't send non-None value to a just-started generatorhatası alırsınız.
itertools ile Generator Gücünü Katlamak
Python'un itertools modülü, generator'larla çalışırken kullanabileceğiniz hazır araçlar sunar. Hepsi tembel çalışır, yani bellek dostu:
from itertools import chain, islice, takewhile, dropwhile, count, cycle, repeat
# chain — birden fazla iterable'ı birleştirir
birlesik = chain([1, 2, 3], [4, 5, 6], [7, 8, 9])
print(list(birlesik))
# Çıktı: [1, 2, 3, 4, 5, 6, 7, 8, 9]
# count — sonsuz sayaç
# count(10, 2) → 10, 12, 14, 16, 18, ...
ilk_bes = list(islice(count(10, 2), 5))
print(ilk_bes)
# Çıktı: [10, 12, 14, 16, 18]
# cycle — iterable'ı sonsuz tekrarlar
# cycle("ABC") → A, B, C, A, B, C, A, ...
tekrar = list(islice(cycle("ABC"), 8))
print(tekrar)
# Çıktı: ['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B']
# takewhile — koşul sağlandığı sürece al
sayilar = [2, 4, 6, 8, 1, 3, 5, 7]
ciftler = list(takewhile(lambda x: x % 2 == 0, sayilar))
print(ciftler)
# Çıktı: [2, 4, 6, 8] (ilk tek sayıda durur)
# dropwhile — koşul sağlandığı sürece atla, sonra hepsini al
geri_kalan = list(dropwhile(lambda x: x % 2 == 0, sayilar))
print(geri_kalan)
# Çıktı: [1, 3, 5, 7] (ilk tek sayıdan itibaren alır)Bu araçları kendi generator'larınızla birleştirerek çok güçlü veri işleme akışları kurabilirsiniz. Hiçbiri belleğe yüklemez, hepsi tembel çalışır.
Yaygın Hatalar ve Tuzaklar
Generator'larla çalışırken herkesin düştüğü tuzaklar var. Bunları bilmek sizi saatlerce debug'dan kurtarır.
Tuzak 1: Generator'ı İki Kez Tüketmeye Çalışmak
Bu en yaygın hata. Generator bir kez tüketilir, ikinci sefer boş döner:
def sayilar_uret():
yield 1
yield 2
yield 3
gen = sayilar_uret()
# İlk kullanım — çalışır
print(list(gen)) # [1, 2, 3]
# İkinci kullanım — BOŞ!
print(list(gen)) # []
# Çözüm 1: Yeni generator oluşturun
gen2 = sayilar_uret()
print(list(gen2)) # [1, 2, 3]
# Çözüm 2: itertools.tee ile kopyalayın (dikkatli kullanın)
from itertools import tee
gen_a, gen_b = tee(sayilar_uret(), 2)
print(list(gen_a)) # [1, 2, 3]
print(list(gen_b)) # [1, 2, 3]⚠️ Dikkat:
itertools.tee()kullanırken dikkatli olun. Eğer bir kopya diğerinden çok ilerdeyse, aradaki tüm elemanlar bellekte tutulur. Büyük veri setlerindetee()bellek avantajınızı sıfırlayabilir.
Tuzak 2: Generator'da len() Kullanmaya Çalışmak
Generator'ların uzunluğu yoktur çünkü elemanlar henüz üretilmemiştir:
gen = (x for x in range(100))
# Bu HATA verir:
# len(gen) → TypeError: object of type 'generator' has no len()
# Eleman sayısını öğrenmek istiyorsanız tüketmeniz gerekir:
adet = sum(1 for _ in gen) # 100 — ama artık gen tükenmiştir!Tuzak 3: Generator İçinde Scope Sorunu
Bu tuzak özellikle döngü içinde generator oluşturanlarda sık görülür:
# YANLIŞ — tüm generator'lar aynı 'i' değerine bağlı
generators_yanlis = [
(x * i for x in range(3))
for i in range(4)
]
# i son değeri olan 3'ü kullanır — sürpriz!
for gen in generators_yanlis:
print(list(gen))
# Beklenmeyen çıktı — tüm generator'lar i=3 ile çalışır
# DOĞRU — lambda ile kapama (closure) oluşturun
def carpan_generator(carpan):
return (x * carpan for x in range(3))
generators_dogru = [carpan_generator(i) for i in range(4)]
for gen in generators_dogru:
print(list(gen))
# Çıktı:
# [0, 0, 0]
# [0, 1, 2]
# [0, 2, 4]
# [0, 3, 6]Tuzak 4: Generator İçinde return ile Değer Kaybetmek
def hatali_generator():
yield 1
yield 2
return 3 # Bu değer for döngüsünde KAYBOLUR
yield 4 # Bu satır asla çalışmaz
for deger in hatali_generator():
print(deger)
# Çıktı: 1 2
# 3 ve 4 asla yazdırılmaz!return generator'ı sonlandırır ve StopIteration fırlatır. return 3 deki 3 değeri StopIteration exception'ının value attribute'una atanır ama for döngüsü bunu yakalar ve görmezden gelir. return'den sonraki yield'lar ise hiçbir zaman ulaşılamaz (unreachable code).
Best Practices: Profesyonel Generator Kullanımı
1. Büyük Veri → Generator, Küçük Veri → Liste
Eğer veri seti RAM'e rahatça sığıyorsa ve birden fazla kez erişmeniz gerekiyorsa, liste kullanın. Generator her durumda doğru tercih değil:
# Küçük veri — liste daha pratik
kullanicilar = [kullanici for kullanici in veritabani.getir_hepsi()] # 50 kayıt
# Büyük veri — generator şart
log_satirlari = (satir for satir in dosya_oku("10gb_log.txt"))2. Generator Pipeline'larını Modüler Tutun
Her aşama tek bir iş yapmalı. Küçük, birleştirilebilir generator'lar yazın:
# İyi — her fonksiyon tek bir iş yapıyor
def oku(kaynak):
...
def filtrele(veriler, kosul):
...
def donustur(veriler, islem):
...
# Birleştir
sonuc = donustur(filtrele(oku("data.csv"), kosul), islem)3. Generator'ı Fonksiyona Geçirirken Paranteze Dikkat
# Fonksiyonun tek argümanı generator ise ekstra parantez gereksiz
toplam = sum(x ** 2 for x in range(10)) # Doğru ve temiz
# Birden fazla argüman varsa parantez gerekir
sonuc = max((x ** 2 for x in range(10)), default=0)4. close() ile Kaynakları Temizleyin
Generator'ınız dosya, ağ bağlantısı gibi kaynaklar kullanıyorsa, try/finally veya close() ile temizleme yapın:
def veritabani_oku(sorgu):
baglanti = veritabani_baglan()
try:
cursor = baglanti.execute(sorgu)
for satir in cursor:
yield satir
finally:
baglanti.kapat() # Generator tüketilsin veya tüketilmesin, kapanır5. Tip Belirteci (Type Hint) Kullanın
Python 3.9+ ile generator'lara tip belirteci ekleyebilirsiniz:
from collections.abc import Generator
def cift_sayilar(limit: int) -> Generator[int, None, None]:
"""Çift sayılar üreten generator
Generator[YieldType, SendType, ReturnType]
"""
for i in range(0, limit, 2):
yield iNe Zaman Generator, Ne Zaman Liste?
| Durum | Tercih | Neden |
|---|---|---|
| Veri RAM'e sığmıyor | Generator | Bellek tasarrufu |
| Veriye birden fazla kez erişim gerekiyor | Liste | Generator tek kullanımlık |
| Sonsuz seri / sürekli akan veri | Generator | Liste sonsuz olamaz |
| Veri pipeline'ı (oku → filtrele → dönüştür) | Generator | Her aşama tembel çalışır |
| Elemanların indeksle erişimi gerekiyor | Liste | Generator indekslenemez |
| Veri setinin uzunluğunu bilmek gerekiyor | Liste | Generator'da len() yok |
| API pagination / dosya okuma | Generator | Gereksiz yükleme yapmaz |
Özet
Iterator,
__iter__()ve__next__()metodlarını implemente eden nesnedir. Python'dakifordöngüsü bu protokolü kullanır.Generator fonksiyon,
yieldanahtar kelimesi içeren fonksiyondur. Çağrıldığında iterator döndürür, kodu çalıştırmaz. Hernext()çağrısındayield'a kadar çalışır ve durur.Generator expression, tek satırlık generator oluşturma yöntemidir.
(x for x in range(n))şeklinde yazılır ve list comprehension'ın tembel versiyonudur.Bellek avantajı devasa: 1 milyon elemanlı listede ~8 MB, aynı generator'da ~200 byte. Büyük veri setlerinde generator kullanmak zorunluluktur, tercih değil.
Tek kullanımlık oldukları en kritik kural: bir generator tüketildikten sonra tekrar kullanılamaz. Birden fazla geçiş gerekiyorsa yeni generator oluşturulmalı veya listeye dönüştürülmeli.
Pipeline'lar generator'ların en güçlü kullanım alanı: oku → filtrele → dönüştür → yaz akışında her aşama tembel çalışır, bellekte her an tek eleman bulunur.
yield fromile alt generator'ları delege edebilir,send()ile generator'a çalışma anında veri gönderebilir,itertoolsmodülü ile hazır tembel araçları kullanabilirsiniz.
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
Bu konuyu derinlemesine öğrenmek ister misin?
Python Programlama: Sıfırdan İleri Seviyeye
İlgili Yazılar
Python Decorators (Dekoratörler): Fonksiyonlarını Güçlendir
Python'da decorator'lar nasıl çalışır, nasıl yazılır ve gerçek projelerde nasıl kullanılır? Sıfırdan ileri seviyeye, kod...
Python Nedir? Neden Python Öğrenmelisiniz?
Python nedir, ne işe yarar? Yapay zeka, veri bilimi, web geliştirme ve otomasyon için neden Python? Kariyer fırsatları v...
Python'da Context Manager ve with Bloğu Derinlemesine
Python context manager: with bloğu, __enter__/__exit__, contextlib, @contextmanager decorator ve gerçek dünya kaynak yön...