Python'da generator fonksiyon, tüm sonuçları tek seferde hesaplayıp bir listede biriktirmek yerine, her değeri ihtiyaç duyulduğu anda tek tek üreten özel bir fonksiyon türüdür. Bu üretim işini mümkün kılan şey, fonksiyon gövdesinde en az bir kez kullanılan yield anahtar kelimesidir. Normal bir fonksiyon return ile sonuçlanıp belleğini boşaltırken, generator fonksiyon kaldığı yeri hatırlar ve her çağrıldığında sıradaki değeri verir.
Bu yazıda generator fonksiyonların çalışma zamanında tam olarak ne yaptığını, generator expression ile aralarındaki sözdizimi farkını, yield ile return karıştırıldığında ortaya çıkan hataları ve büyük veri kümelerinde generator kullanmanın sağladığı bellek avantajını adım adım ele alacağız. Ayrıca next() fonksiyonu, for döngüsü ve StopIteration istisnasının generator tüketimindeki rolünü uygulamalı bir dosya okuma örneğiyle somutlaştıracağız.
Generator Fonksiyon Nedir, Normal Fonksiyondan Ne Farkı Vardır?
Bir fonksiyonu generator fonksiyon yapan tek şart, gövdesinde en az bir yield ifadesinin bulunmasıdır. Bu tek satır, fonksiyonun Python yorumlayıcısı tarafından tamamen farklı ele alınmasına yol açar: fonksiyon artık normal bir alt program gibi değil, bir iterator üreten bir yapı gibi davranır. Iterator, sırayla bir sonraki değeri döndürebilen ve bittiğinde bunu bildiren nesne demektir; generator fonksiyonlar bu iterator protokolünü otomatik olarak uygular.
Normal bir fonksiyonda kod, fonksiyon çağrıldığı anda baştan sona çalışır ve return satırına ulaştığında bir değer üretip fonksiyonu tamamen sonlandırır. Generator fonksiyonda ise durum tamamen farklıdır: fonksiyon çağrıldığında kod gövdesi hemen çalışmaz, bunun yerine bir generator nesnesi döndürülür. Kodun gerçekten çalışmaya başlaması için bu nesne üzerinde next() çağrılması ya da bir for döngüsüne verilmesi gerekir. Aşağıdaki örnek bu farkı net biçimde gösterir:
def sayilari_uret():
print("Fonksiyon başladı")
yield 1
yield 2
yield 3
g = sayilari_uret()
print("Generator nesnesi oluştu, henüz kod çalışmadı")
print(next(g))
Bu kodu çalıştırdığınızda "Generator nesnesi oluştu, henüz kod çalışmadı" satırının, fonksiyon içindeki print ifadesinden önce ekrana bastığını görürsünüz; çünkü sayilari_uret() çağrısı yalnızca bir generator nesnesi yaratmıştır, kod gövdesini çalıştırmamıştır. Kodun gerçekten yürümesi ancak next(g) çağrıldığında başlar. Bu davranış, generator fonksiyonları normal fonksiyonlardan ayıran en temel özelliktir ve ilerleyen bölümlerde yield'in çalışma zamanı mekaniğini, next() ile for döngüsü üzerinden tüketimini ve pratik bir dosya okuma örneğini adım adım işleyeceğiz. Bu mantığı sıfırdan, uygulamalı biçimde pekiştirmek isteyenler birebir Python dersleri kapsamında generator ve iterator konularını canlı örneklerle çalışabilir.
yield Anahtar Kelimesi Çalışma Zamanında Tam Olarak Nasıl İşler?
yield ile return arasındaki fark yalnızca sözdizimsel değil, çalışma zamanı davranışı açısından da köklüdür. return bir fonksiyonu tamamen sonlandırır; fonksiyonun yerel değişkenleri bellekten silinir ve fonksiyon bir daha kaldığı yerden devam edemez. yield ise fonksiyonu sonlandırmaz, yalnızca duraklatır. Duraklama anında fonksiyonun tüm yerel değişkenleri ve kod içindeki konumu (hangi satırda kaldığı) korunur; bir sonraki çağrıda fonksiyon sıfırdan değil, tam olarak kaldığı satırdan devam eder.
Bir generator üzerinde next() çağrıldığında yorumlayıcının izlediği adımlar şu şekilde özetlenebilir:
- Generator fonksiyon çağrılır.
- Kod gövdesi çalışmaz; bunun yerine bir generator nesnesi oluşturulur.
- Bu nesne üzerinde
next()çağrılır. - Kod, fonksiyon gövdesinde ilk
yieldifadesine kadar çalışır. yield'deki değer dışarıya döner ve fonksiyon tam o noktada duraklar.- Tekrar
next()çağrıldığında fonksiyon kaldığı yerden, aynı yerel değişkenlerle devam eder. - Fonksiyon gövdesinde çalıştırılacak başka
yieldkalmayıncaStopIterationistisnası fırlatılır.
Bu adımları somutlaştıran basit bir örnek şöyledir:
def sayac(sinir):
sayi = 1
while sayi <= sinir:
yield sayi
sayi += 1
g = sayac(3)
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # StopIteration hatası fırlatır
Buradaki sayi değişkeni, her yield sonrası duraklamada bellekte saklanır; fonksiyon tekrar çağrıldığında sayi sıfırlanmaz, kaldığı değerden devam eder. Dördüncü next(g) çağrısında ise döngü koşulu artık sağlanmadığından fonksiyon gövdesinde çalıştırılacak yield kalmaz ve Python otomatik olarak StopIteration istisnasını fırlatarak generator'ın tükendiğini bildirir. Bu istisna genellikle doğrudan görünmez, çünkü for döngüsü bunu arka planda yakalayıp döngüyü sessizce sonlandırır.
Generator Fonksiyon ile Generator İfadesi (Generator Expression) Farkı

Generator oluşturmanın iki yolu vardır: def ve yield ile yazılan tam teşekküllü generator fonksiyon, ya da tek satırlık generator expression. Generator expression, list comprehension'a çok benzer bir sözdizimine sahiptir; tek fark, köşeli parantez [] yerine normal parantez () kullanılmasıdır. Örneğin [x*x for x in range(5)] bir liste üretirken, (x*x for x in range(5)) aynı değerleri tek tek üreten bir generator nesnesi döndürür:
kareler_listesi = [x*x for x in range(5)] # [0, 1, 4, 9, 16]
kareler_generator = (x*x for x in range(5)) # generator nesnesi
for kare in kareler_generator:
print(kare)
İki yöntem arasındaki seçim, mantığın karmaşıklığına bağlıdır. Tek bir ifadeyle, koşulsuz ya da tek koşullu basit bir dönüşüm yapılacaksa generator expression daha okunaklı ve kısa bir çözümdür. Buna karşılık birden fazla adım, koşullu dallanma, döngü içinde döngü ya da hata yönetimi gerektiren durumlarda def ile yazılan generator fonksiyon çok daha esnek ve okunabilir kalır; çünkü fonksiyon gövdesi içinde istediğiniz kadar satır, koşul ve hatta birden fazla yield ifadesi kullanabilirsiniz. Görsel olarak comprehension'a benzemesi bazı yeni başlayanları yanıltabilir; ancak generator expression'ın sonucu asla anında bir liste değildir, yalnızca talep üzerine değer üreten bir nesnedir ve içeriği ancak bir kez dolaşılabilir. Bu tür ince ayrımları örneklerle pratik ederek pekiştirmek isteyenler video Python eğitimleri üzerinden konuyu kendi hızlarında tekrar tekrar izleyebilir.
Normal Fonksiyon ile Generator Fonksiyon Karşılaştırması
Bir fonksiyonun içine yield eklemek, o fonksiyonun Python yorumlayıcısı tarafından ele alınış biçimini kökten değiştirir. Normal bir fonksiyon çağrıldığında hesaplamasını baştan sona tamamlar ve tek bir sonucu return ile geri verip belleğini serbest bırakır; generator fonksiyon ise çağrıldığı anda hiçbir kod satırını çalıştırmaz, yalnızca bir generator nesnesi üretir ve gerçek işlemler ancak siz değer istediğinizde adım adım yürür. Aşağıdaki tablo bu iki yaklaşımın beş temel eksende nasıl ayrıştığını özetler.
| Özellik | Normal Fonksiyon | Generator Fonksiyon |
|---|---|---|
| Dönüş türü | Tek bir değer veya veri yapısı (liste, sözlük, sayı vb.) | Generator nesnesi; değerler next() çağrıldıkça tek tek üretilir |
| Bellek kullanımı | Tüm sonuç önceden hesaplanıp bellekte tutulur | Yalnızca o an üretilen değer bellekte tutulur, geçmiş değerler saklanmaz |
| Çalışma zamanı davranışı | Çağrıldığı anda gövde tamamen çalışır | Gövde, her next() çağrısında bir sonraki yield'e kadar ilerler |
| Tekrar çağrılabilirlik | Her çağrıda işlem baştan yeniden yapılır | Bir kez tüketilen generator nesnesi yeniden baştan çalışmaz; yeni bir nesne oluşturulmalıdır |
| Sonlanma şekli | return ile veya fonksiyon gövdesinin sonuna gelerek biter |
Üretilecek değer kalmayınca StopIteration istisnası fırlatılarak sonlanır |
Bu tablodan çıkarılacak en önemli sonuç şudur: normal fonksiyon "sonucu hazırla ve teslim et" mantığıyla çalışırken generator fonksiyon "istendikçe üret" mantığıyla çalışır. Bu fark, özellikle büyük veri kümeleriyle çalışırken performans ve bellek yönetimi açısından belirleyici hale gelir.
yield ile return Karıştırılınca Ortaya Çıkan Tipik Hatalar

Generator mantığına yeni başlayan öğrencilerin karşılaştığı hataların büyük bölümü, yield içeren bir fonksiyonun normal fonksiyon gibi davranacağı varsayımından kaynaklanır. Bu bölümde en sık görülen üç hatayı ve doğru kullanımlarını inceleyelim.
Hata 1: Generator Nesnesini Değer Sanmak
İçinde yield bulunan bir fonksiyonu çağırıp doğrudan yazdırmaya çalışmak, beklenen sayı veya metin yerine bir generator nesnesinin adresini ekrana basar:
def sayilari_uret():
yield 1
yield 2
yield 3
print(sayilari_uret()) # yazdırır, 1 değil
Doğru kullanım, nesneyi next() ile beslemek ya da bir for döngüsüne vermektir; bu sayede değerler tek tek elde edilir.
Hata 2: Generator İçinde return Kullanarak StopIteration'ı Erken Tetiklemek
Bir generator fonksiyonun içinde return ifadesi kullanmak, o fonksiyonu normal bir fonksiyona çevirmez; bunun yerine mevcut değeri döndürmeden generator'ı doğrudan sonlandırır ve StopIteration istisnasının erkenden tetiklenmesine yol açar:
def sinirli_uret(n):
sayac = 0
while True:
if sayac == n:
return # döngüyü burada bitirir, deger dondurmez
yield sayac
sayac += 1
for deger in sinirli_uret(3):
print(deger) # 0, 1, 2 yazdırır, 3 yazdırılmaz
Burada return, generator'ı bilinçli olarak sonlandırmak için doğru bir araçtır; ancak beklenmedik yerde kullanıldığında döngünün erken kesildiğini fark etmemek yaygın bir hatadır. Konuyu netleştirmek isteyen ve seviyesini ölçmek isteyen öğrenciler Python bilgi testi ile bu tarz akış kontrolü sorularını pratik edebilir.
Hata 3: Döngü İçinde Yanlış Konumlandırılmış return
Bazı öğrenciler döngü gövdesinin en başına yanlışlıkla return yazar ve generator ilk turdan sonra hiç değer üretmeden durur:
def hatali_uret(liste):
for eleman in liste:
return eleman # dongu ilk elemanda biter, yield olmali
Doğrusu, döngü içinde her elemanı yield ile teslim etmek, fonksiyonu tamamen bitirmek istendiğinde ise döngü dışında veya koşula bağlı olarak return kullanmaktır.
Büyük Veri Kümelerinde Generator Kullanmanın Bellek Avantajı
Bir listeyle çalışırken Python, listenin tüm elemanlarını önceden hesaplayıp bellekte saklar; milyonlarca elemanlı bir liste oluşturduğunuzda bu elemanların tamamı, siz onları kullanana kadar bellekte bekler. Generator ise tam tersi bir strateji izler: değerleri önceden üretip depolamaz, yalnızca kendisinden bir sonraki değer istendiğinde hesaplama yapar ve o değeri üretir. Bu yaklaşıma tembel değerlendirme (lazy evaluation) denir ve bellek tüketimini doğrudan etkiler.
Bu farkın pratikte nasıl işlediğini bir senaryoyla düşünelim: elinizde satır sayısı çok yüksek bir günlük (log) dosyası veya devasa bir sayısal veri kümesi olduğunu varsayalım. Bu veriyi bir listeye dönüştürüp işlemeye çalışırsanız, programın çalışması için gereken bellek miktarı veri boyutuyla doğru orantılı büyür; sistem kaynakları yetersiz kalırsa program yavaşlar veya çöker. Aynı veriyi bir generator aracılığıyla satır satır ya da eleman eleman işlediğinizde ise bellekte her an yalnızca "şu anki" veri parçası bulunur, geri kalan veri henüz üretilmemiş durumdadır.
Bu nedenle generator'lar özellikle şu tür durumlarda tercih edilir:
- Boyutu bilinmeyen veya çok büyük olan dosyaların okunması
- Sonsuz veya çok uzun sayı dizilerinin (örneğin belirli bir kurala göre üretilen sayılar) temsil edilmesi
- Veri işleme hattında (pipeline) her adımın bir öncekinden gelen veriyi teker teker işlemesi
- Bellek sınırlı ortamlarda (küçük sunucular, gömülü sistemler) çalışan uygulamalar
Özetle, listeler "her şeyi şimdiden hazırla" felsefesiyle çalışırken generator'lar "gerektiğinde üret" felsefesiyle çalışır. Bu felsefe farkı, kod her iki durumda da aynı sonucu üretse bile, program büyük veriyle karşılaştığında hangi yaklaşımın sürdürülebilir olduğunu belirleyen temel etkendir.
next() Fonksiyonu ve for Döngüsüyle Generator Tüketimi
Bir generator fonksiyonu çağırdığında elde ettiğin şey, hazır bir sonuç değil, iterator protokolüne uyan bir generator nesnesidir. Bu nesneden değerleri tek tek çekmenin iki temel yolu vardır: next() fonksiyonuyla manuel kontrol veya for döngüsüyle otomatik tüketim. İkisi de aynı mekanizmayı kullanır, ama davranış biçimleri öğrencinin ne zaman hangisini seçeceğini anlaması açısından önemlidir.
next() fonksiyonu, generator'ı bir sonraki yield ifadesine kadar çalıştırır ve orada üretilen değeri döndürür. Generator'ın üretecek başka değeri kalmadığında ise StopIteration istisnası fırlatılır. Aşağıdaki örnek bu davranışı net biçimde gösterir:
def sayilari_uret():
yield 1
yield 2
yield 3
uretici = sayilari_uret()
print(next(uretici)) # 1
print(next(uretici)) # 2
print(next(uretici)) # 3
print(next(uretici)) # StopIteration hatası fırlatır
for döngüsü ise bu StopIteration istisnasını arka planda otomatik olarak yakalar ve döngüyü sessizce sonlandırır; geliştiricinin bunu ayrıca try-except ile ele alması gerekmez. Aynı generator'ı bir for döngüsüyle tükettiğinde kod çok daha sade görünür:
for sayi in sayilari_uret():
print(sayi)
Pratikte fark şuraya çıkar: next() ile manuel kontrol, generator'ın üretimini belirli bir noktada duraklatıp başka bir işlem yaptıktan sonra kaldığı yerden devam etmen gerektiğinde, ya da iki generator'dan sırayla değer almak gibi ince kontrol gerektiren senaryolarda tercih edilir. for döngüsü ise generator'ın ürettiği tüm değerleri baştan sona, sırayla işlemek istediğin standart durumların neredeyse tamamını karşılar ve günlük kullanımda çok daha yaygındır. Bu ayrımı pekiştirmek isteyen ve iterator, döngü gibi temel konulardaki bilgisini ölçmek isteyenler ücretsiz kodlama bilgisi testini çözerek kendi seviyesini görebilir.
Uygulamalı Örnek: Büyük Metin Dosyasını Satır Satır Okuyan Generator
Generator fonksiyonların en somut faydası, büyük dosyalarla çalışırken ortaya çıkar. Bir log dosyasını veya devasa bir CSV dosyasını klasik yöntemle readlines() kullanarak okumak, dosyanın tamamını belleğe yükler. Generator kullanan bir çözümde ise satırlar tek tek, ihtiyaç anında okunur. Aşağıdaki kod bunu gösteren çalışır bir örnektir:
def satirlari_oku(dosya_yolu):
with open(dosya_yolu, "r", encoding="utf-8") as dosya:
for satir in dosya:
yield satir.strip()
satir_sayisi = 0
for satir in satirlari_oku("buyuk_log.txt"):
satir_sayisi += 1
if "HATA" in satir:
print(f"{satir_sayisi}. satırda hata bulundu: {satir}")
print(f"Toplam işlenen satır: {satir_sayisi}")
Bu kodda kritik nokta, with open(...) bloğunun dosyayı generator fonksiyonun içinde açması ve her yield çağrısında yalnızca o anki satırı üretmesidir. Dosyanın tamamı hiçbir zaman tek seferde belleğe yüklenmez; for döngüsü bir sonraki satırı istediğinde generator kaldığı yerden devam eder, dosyadan bir satır daha okur ve onu üretir. Dosya ister birkaç kilobayt ister birkaç gigabayt olsun, bellek kullanımı neredeyse sabit kalır çünkü aynı anda bellekte tutulan veri miktarı her zaman "o anki satır" ile sınırlıdır.
Bu yaklaşım özellikle log dosyaları, büyük CSV kayıtları veya satır satır işlenen ham metin veri setleriyle çalışırken tercih edilir; çünkü dosya boyutu ne olursa olsun program aynı bellek disipliniyle çalışmaya devam eder ve bellek yetersizliği riski ortadan kalkar. Dosya işleme, iterator mantığı ve benzeri konularda temelden ileri seviyeye adım adım ilerlemek isteyen öğrenciler için birebir Python özel dersleri bu tür gerçek dünya senaryolarını uygulamalı biçimde ele alan bir çalışma ortamı sunar.
Sık Sorulan Sorular
Generator fonksiyon çağrıldığında neden direkt bir değer değil de generator nesnesi döner?
Çünkü içinde yield geçen bir fonksiyon Python tarafından normal fonksiyondan farklı işlenir; çağrıldığında fonksiyon gövdesi hemen çalışmaz, bunun yerine değerleri tembel (lazy) biçimde üretecek bir generator nesnesi oluşturulur. Fonksiyon gövdesinin gerçek çalışması, ancak next() çağrıldığında veya bir for döngüsü değeri talep ettiğinde başlar.
Bir fonksiyonda hem return hem yield birlikte kullanılabilir mi?
Evet, ama anlamları farklıdır. yield içeren bir fonksiyonda değer taşımayan sade bir return ifadesi, generator'ı sonlandırıp StopIteration tetiklemek için kullanılabilir; fonksiyonu normal bir fonksiyon gibi tek bir değer döndürmesi için kullanamazsın, çünkü içinde yield bulunan her fonksiyon otomatik olarak generator fonksiyona dönüşür.
Generator'ı bir kez tükettikten sonra tekrar baştan kullanabilir miyim?
Hayır, bir generator nesnesi yalnızca bir kez baştan sona tüketilebilir. Tüm değerler üretildikten sonra generator "tükenmiş" durumda kalır ve tekrar aynı verileri elde etmek istersen fonksiyonu yeniden çağırıp yeni bir generator nesnesi oluşturman gerekir.
yield ile return'ü ne zaman birbirine karıştırdığımı nasıl anlarım?
Fonksiyonundan beklediğin tek bir sonuç yerine <generator object ...> gibi bir çıktı aldığında veya döngüde beklenmedik biçimde tek değer yerine bir dizi ara değer dolaştığını fark ettiğinde, muhtemelen fonksiyonun içinde istemeden yield kullanmışsındır ya da tam tersi, tek bir değerle yetinip devam etmesi gereken bir üretim döngüsünü return ile erken kesmişsindir.
Generator expression ile list comprehension arasındaki temel fark nedir?
List comprehension köşeli parantezle yazılır ve sonucu anında tam bir liste olarak bellekte oluşturur; generator expression ise normal parantezle yazılır ve değerleri talep üzerine, tek tek üretir, bu yüzden büyük veri kümelerinde bellek açısından daha tasarrufludur.
Generator fonksiyonların performans üzerinde her zaman avantajı var mıdır?
Hayır, her zaman değil. Generator'lar bellek kullanımında avantaj sağlar çünkü verinin tamamını aynı anda tutmaz; ancak aynı veri üzerinde birden fazla kez, tekrar tekrar gezinmen gereken küçük veri kümelerinde normal bir liste kullanmak hem daha basit hem de bazen daha hızlı olabilir, çünkü listeye rastgele erişim mümkündür, generator'a ise değildir.
next() fonksiyonunu generator tükendikten sonra çağırırsam ne olur?
Generator'ın üretecek başka değeri kalmadığı andan itibaren yapılan her next() çağrısı StopIteration istisnası fırlatır. Bu durum genellikle manuel next() kullanımında, döngü bittiği hâlde fonksiyonun tekrar çağrılması sonucu ortaya çıkan tipik bir hatadır ve programın try-except ile ele alınmadığı sürece çökmesine neden olabilir.
Generator fonksiyonlar ve yield anahtar kelimesi, Python'da bellek dostu ve okunabilir kod yazmanın temel yapı taşlarından biridir; bu mantığı iyice oturttuktan sonra iterator protokolü, dosya işleme ve veri akışı gibi konularda çok daha rahat ilerlersin. Konuyu birebir bir eğitmenle pekiştirmek isteyen öğrenciler özel ders randevu sistemini inceleyerek kendi seviyesine uygun bir çalışma planı oluşturabilir.