Python'da generator (üreteç), gövdesinde yield geçen bir fonksiyonun çağrılmasıyla elde edilen ve değerleri tek tek, ancak istendiğinde üreten bir iterator nesnesidir. Fonksiyonu çağırdığınızda gövde hemen çalışmaz; her next() çağrısında kod bir sonraki yield ifadesine kadar ilerler, değeri verir ve tüm yerel durumunu koruyarak bekler. Böylece milyonlarca öğeyi belleğe almadan işleyebilir, sonsuz diziler tanımlayabilir ve adım adım çalışan veri hatları kurabilirsiniz.
Bu yazıda önce generator'ların üzerine kurulduğu iterator protokolünü (iter(), next(), StopIteration) açıklıyor, ardından generator fonksiyonlarını, generator ifadelerini, yield from ile yetki devrini, send()/close() yöntemlerini ve sık yapılan hataları ele alıyoruz. Tüm örnekler Python 3.15.2 ile çalıştırıldı. Serinin genel haritası için Python öğrenme rehberi sayfasına bakabilirsiniz.
Iterable ve iterator: farkı nedir?
Python'da for döngüsünün üzerinde dolaşabildiği her nesneye iterable (yinelenebilir) denir: liste, tuple, str, dict, set, dosya nesnesi ve range bunlara örnektir. Iterator ise akışı temsil eden ayrı bir nesnedir: __next__() yöntemi her çağrıldığında bir sonraki öğeyi döndürür, öğe kalmadığında StopIteration istisnası yükseltir. Yerleşik iter() fonksiyonu bir iterable'dan iterator üretir; nesne yinelemeyi desteklemiyorsa TypeError verir.
Protokolün önemli bir kuralı, iterator'ın __iter__() yönteminin nesnenin kendisini döndürmesidir. Bu sayede bir iterator'ı doğrudan for döngüsüne verebilirsiniz. Listenin kendisi ise iterator değildir; her iter() çağrısında yeni ve bağımsız bir iterator üretir. Aşağıdaki örnekte bu farkı görebilirsiniz. next() fonksiyonuna ikinci argüman verirseniz, akış bittiğinde istisna yerine bu varsayılan değer döner.
sayilar = [10, 20, 30]
it = iter(sayilar)
print(next(it))
print(next(it))
print(next(it))
print(next(it, 'bitti'))
print(iter(it) is it)
print(iter(sayilar) is sayilar)
# çıktı: 10
# çıktı: 20
# çıktı: 30
# çıktı: bitti
# çıktı: True
# çıktı: FalseIterator'lar yalnızca ileri gider. Resmî Functional Programming HOWTO bunu açıkça vurgular: protokol yalnızca __next__() yöntemini şart koşar; önceki öğeye dönmenin, iterator'ı sıfırlamanın ya da kopyalamanın standart bir yolu yoktur. Aynı veriyi ikinci kez dolaşmanız gerekiyorsa yeni bir iterator oluşturmanız gerekir. for döngüsü arka planda tam olarak bunu yapar: önce iter() çağırır, sonra StopIteration gelene kadar next() ile ilerler. Döngü sözdiziminin ayrıntıları için Python for ve while döngüleri yazısına bakabilirsiniz.
Sınıfla iterator yazmak
Kendi iterator'ınızı yazmanın klasik yolu, __iter__() ve __next__() yöntemlerini tanımlayan bir sınıftır. Resmî Python Tutorial'daki örneğe benzer şekilde, aşağıdaki sınıf verilen sayıdan geriye doğru sayar. Durumu (self.n) nesnenin özniteliğinde tutmanız ve sonunda StopIteration yükseltmeyi unutmamanız gerekir.
class GeriSayim:
def __init__(self, baslangic):
self.n = baslangic
def __iter__(self):
return self
def __next__(self):
if self.n <= 0:
raise StopIteration
self.n -= 1
return self.n + 1
for x in GeriSayim(3):
print(x)
# çıktı: 3
# çıktı: 2
# çıktı: 1Bu yaklaşım çalışır ama oldukça fazla tören içerir: durumu elle saklarsınız, bitiş koşulunu elle kontrol edersiniz, istisnayı elle yükseltirsiniz. Özel metotların genel mantığı için Python özel metotlar (dunder) yazısına bakabilirsiniz. Generator fonksiyonları ise aynı işi çok daha kısa yoldan yapar.
Generator fonksiyonu ve yield
Language Reference'a göre bir fonksiyonun gövdesinde yield ifadesi bulunması, o fonksiyonu generator fonksiyonuna dönüştürür. Böyle bir fonksiyonu çağırdığınızda gövde çalışmaz; bunun yerine generator adı verilen bir iterator nesnesi döner. Çalışma, generator'ın yöntemlerinden biri (genellikle next() üzerinden __next__()) çağrıldığında başlar ve ilk yield ifadesine kadar sürer. Orada fonksiyon askıya alınır: yerel değişkenler, kodun nerede kaldığı ve istisna işleme durumu dahil her şey korunur. Bir sonraki çağrıda kaldığı yerden devam eder.
def geri_sayim(n):
print('başladı')
while n > 0:
yield n
n -= 1
print('bitti')
g = geri_sayim(2)
print(type(g).__name__)
print(next(g))
print(next(g))
try:
next(g)
except StopIteration:
print('StopIteration geldi')
# çıktı: generator
# çıktı: başladı
# çıktı: 2
# çıktı: 1
# çıktı: bitti
# çıktı: StopIteration geldiÇıktıya dikkat edin: "başladı" yazısı generator oluşturulduğunda değil, ilk next() çağrısında görünüyor. "bitti" ise döngü sona erip fonksiyon gövdesinin sonuna ulaşıldığında, yani üçüncü next() çağrısında yazdırılıyor; hemen ardından StopIteration yükseliyor. Generator fonksiyonu return ile ya da gövdenin sonuna gelerek bittiğinde Python otomatik olarak StopIteration üretir; sizin yükseltmeniz gerekmez.
Generator'lar __iter__() ve __next__() yöntemlerini otomatik olarak edinir. Bu yüzden sınıflı geri sayım örneğini dört satırlık bir fonksiyona indirebilirsiniz. Tutorial da aynı noktayı vurgular: sınıf tabanlı iterator'larla yapılabilen her şey generator'larla da yapılabilir, ama generator'lar daha kompakt olur çünkü yerel değişkenler ve çalışma durumu çağrılar arasında kendiliğinden saklanır.
Generator ifadeleri

Basit dönüşümler için ayrı bir fonksiyon yazmanız gerekmez. Generator ifadesi, list comprehension ile aynı sözdizimini kullanır ama köşeli parantez yerine normal parantez kullanır: (x * x for x in range(4)). Sonuç bir liste değil, değerleri talep edildikçe hesaplayan bir generator'dır. Fonksiyona tek argüman olarak verildiğinde dıştaki parantezleri yazmanıza gerek yoktur: sum(x * 2 for x in range(5)) geçerlidir. Ancak birden fazla argüman varsa generator ifadesini parantez içine almanız gerekir; aksi halde Python 3.15.2 "Generator expression must be parenthesized" iletisiyle SyntaxError verir.
import sys
liste = [x * 2 for x in range(1_000_000)]
uretec = (x * 2 for x in range(1_000_000))
print(sys.getsizeof(liste))
print(sys.getsizeof(uretec))
print(sum(x * 2 for x in range(5)))
try:
compile('print(x for x in range(3), 1)', 'ornek.py', 'exec')
except SyntaxError as e:
print(e.msg)
# çıktı: 8448728
# çıktı: 216
# çıktı: 20
# çıktı: Generator expression must be parenthesizedİlk iki satırın değerleri platforma göre değişebilir, ama aradaki fark kavramı iyi gösterir: bir milyon öğeli liste bütün öğeleri bellekte tutarken generator ifadesi yalnızca küçük bir nesneden ibarettir, çünkü değerleri sırası geldikçe üretir. Sonucu yalnızca bir kez dolaşacak ve bir toplama, en büyük değer arama ya da filtreleme gibi bir işlemden geçirecekseniz generator ifadesi doğal seçimdir. Comprehension sözdiziminin ayrıntıları Python list comprehension yazısında anlatılıyor.
| Özellik | List comprehension | Generator ifadesi |
|---|---|---|
| Sözdizimi | [x * 2 for x in veri] | (x * 2 for x in veri) |
| Ne zaman hesaplanır | Hemen, tamamı | Talep edildikçe, tek tek |
| Bellek | Tüm öğeler bellekte | Yalnızca o anki durum |
| Tekrar dolaşma | İstediğiniz kadar | Bir kez; sonra tükenir |
| len() ve indeks | Destekler | Desteklemez (TypeError) |
| Sonsuz kaynak | Kullanılamaz | islice ile kullanılabilir |
Python 3.15: generator ifadesinde açma (*)
Python 3.15 ile gelen PEP 798, comprehension'larda ve generator ifadelerinde * ile açmaya izin verir. What's New belgesine göre (*L for L in listeler) ifadesi, iç içe (x for L in listeler for x in L) yazımının kısa karşılığıdır. Aynı değişiklik asenkron generator ifadelerini de kapsar. Bu sürümün diğer yenilikleri için Python 3.15 yenilikleri yazısına bakın.
listeler = [[1, 2], [3], [4, 5]]
gen = (*L for L in listeler)
print(list(gen))
# çıktı: [1, 2, 3, 4, 5]Tükenme: generator yalnızca bir kez dolaşılır

Generator'lar ile ilgili en sık karşılaşılan şaşkınlık, ikinci dolaşmada hiçbir şey dönmemesidir. Generator bir iterator olduğu için sonuna ulaştıktan sonra boş kalır; ikinci sum() çağrısı 0, list() çağrısı ise boş liste döndürür. Hata vermez, sessizce boş döner; bu yüzden fark edilmesi zordur.
kareler = (x * x for x in range(4))
print(sum(kareler))
print(sum(kareler))
print(list(kareler))
# çıktı: 14
# çıktı: 0
# çıktı: []Çözüm basittir: değerleri birden fazla kez kullanacaksanız ya sonucu bir listeye çevirip saklayın ya da her kullanımda generator'ı yeniden oluşturun. Generator fonksiyonu kullanıyorsanız ikinci yol çok kolaydır; fonksiyonu yeniden çağırmanız yeterlidir. Aşağıdaki veri hattı örneğinde ayir(temiz(satirlar)) ifadesi iki kez yazılmıştır; bu kasıtlıdır, çünkü ilk zincir list() tarafından tüketilmiştir.
Veri hattı kurmak: generator'ları zincirlemek
Generator'ların en güçlü kullanımlarından biri, her biri tek bir iş yapan küçük adımları birbirine bağlamaktır. Her adım bir önceki adımın çıktısını tüketir ve kendi çıktısını üretir. Hiçbir aşama verinin tamamını belleğe almaz; bir satır, bütün hattan geçip işlendikten sonra sıradaki satır okunur. Dosya nesneleri de iterable olduğu için gerçek bir projede satirlar listesinin yerine açık bir dosyayı verebilirsiniz; dosya okuma ayrıntıları Python dosya okuma ve yazma yazısında.
satirlar = ['# stok listesi', 'elma,3', '', 'armut,5', 'kiraz,2']
def temiz(satirlar):
for s in satirlar:
s = s.strip()
if s and not s.startswith('#'):
yield s
def ayir(satirlar):
for s in satirlar:
ad, adet = s.split(',')
yield ad, int(adet)
print(list(ayir(temiz(satirlar))))
print(sum(adet for _, adet in ayir(temiz(satirlar))))
# çıktı: [('elma', 3), ('armut', 5), ('kiraz', 2)]
# çıktı: 10Bu yapının iki avantajı vardır. Birincisi okunabilirlik: temiz yalnızca boş ve yorum satırlarını atar, ayir yalnızca ayrıştırır. İkincisi tembellik: hattın sonundaki tüketici (burada sum() ya da list()) değer istemedikçe hiçbir adım çalışmaz. Gerçek CSV dosyaları için kendi ayrıştırıcınızı yazmak yerine Python csv modülü kullanmanız daha güvenlidir; buradaki örnek yalnızca deseni göstermek içindir.
Sonsuz generator ve itertools.islice
Generator değerleri talep edildikçe ürettiği için sonsuz bir dizi tanımlamak tamamen meşrudur. HOWTO belgesi de iterator'ların sonlu olmak zorunda olmadığını belirtir. Dikkat etmeniz gereken nokta tüketicidir: list(), max(), min() ya da in operatörünü sonsuz bir generator üzerinde kullanırsanız program asla dönmez. Sınırlı sayıda değer almak için itertools.islice() kullanın.
from itertools import islice
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
print(list(islice(fibonacci(), 10)))
# çıktı: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]islice, count, chain ve batched gibi iterator araçları Python itertools ve functools yazısında ayrıntılı anlatılıyor. Bu araçların hepsi tembel çalışır, yani generator'larla doğal biçimde birleşir.
yield from ile alt generator'a yetki devretmek
Python 3.3 ile gelen yield from ifade sözdizimi, verilen iterable'ın ürettiği bütün değerleri doğrudan dış generator'ın çağıranına aktarır. Language Reference'a göre send() ile gönderilen değerler ve throw() ile atılan istisnalar da alt iterator destekliyorsa ona iletilir. Alt generator bir değer return ettiğinde, bu değer StopIteration istisnasının value özniteliği üzerinden yield from ifadesinin sonucu olur.
def alt():
yield 1
yield 2
return 'alt bitti'
def ust():
sonuc = yield from alt()
print('alt üreteç döndürdü:', sonuc)
yield 3
print(list(ust()))
def duzlestir(veri):
for oge in veri:
if isinstance(oge, list):
yield from duzlestir(oge)
else:
yield oge
print(list(duzlestir([1, [2, [3, 4]], 5])))
# çıktı: alt üreteç döndürdü: alt bitti
# çıktı: [1, 2, 3]
# çıktı: [1, 2, 3, 4, 5]İkinci örnek, iç içe listeleri özyinelemeli olarak düzleştiren klasik bir kullanım. yield from olmadan aynı işi yapmak için iç generator'ı bir for döngüsüyle dolaşıp her öğeyi tek tek yield etmeniz gerekirdi. Basit değer aktarımında iki yazım aynı sonucu verir; fark, send(), throw() ve dönüş değeri gibi ileri özelliklerin de doğru biçimde iletilmesindedir. Not: yield from asenkron generator fonksiyonları içinde kullanılamaz; orada sözdizimi hatasıdır.
send(), throw() ve close(): generator ile iki yönlü iletişim
yield aslında bir ifadedir ve bir değer döndürür. Generator next() ile devam ettirilirse bu değer None olur; send(deger) ile devam ettirilirse gönderilen değer olur. Bu özellik generator'ı, dışarıdan veri alan küçük bir durum makinesine çevirir. Aşağıdaki örnek, gönderilen her sayıdan sonra güncel ortalamayı veren bir generator'dır.
def ortalama():
toplam = 0
adet = 0
ort = None
try:
while True:
deger = yield ort
toplam += deger
adet += 1
ort = toplam / adet
finally:
print('temizlik yapıldı')
o = ortalama()
next(o)
print(o.send(10))
print(o.send(20))
print(o.send(60))
o.close()
y = ortalama()
try:
y.send(5)
except TypeError as e:
print(e)
# çıktı: 10.0
# çıktı: 15.0
# çıktı: 30.0
# çıktı: temizlik yapıldı
# çıktı: can't send non-None value to a just-started generatorBurada üç ayrıntıya dikkat edin. Birincisi, generator'ı ilk yield'e kadar ilerletmek için önce next(o) çağrılır. Yeni oluşturulmuş bir generator'a None dışında bir değer göndermeye çalışırsanız, son satırlarda görüldüğü gibi TypeError alırsınız. İkincisi, close() çağrısı generator'ın askıda olduğu noktada GeneratorExit yükseltir; böylece finally bloğu çalışır ve temizlik yapılır. Üçüncüsü, throw() yöntemi generator'ın içine, askıda olduğu noktadan bir istisna fırlatır; generator bunu yakalayıp devam edebilir ya da sonlanabilir.
Language Reference, yield ifadelerinin try bloğunun her yerinde kullanılabileceğini ve generator sonlandırılmadan önce devam ettirilmezse (referans sayısı sıfıra düştüğünde ya da çöp toplayıcı tarafından toplandığında) close() yönteminin otomatik çağrılacağını belirtir. Yani bekleyen finally blokları sonunda çalışır; yine de dosya ya da bağlantı gibi kaynakları ne zaman serbest bırakacağınızı kesin bilmek istiyorsanız close() çağrısını kendiniz yapın veya kaynağı with bloğu ve context manager ile yönetin. Kapatılmış bir generator'dan yeni değer gelmez:
def gen():
try:
yield 1
yield 2
finally:
print('finally çalıştı')
g = gen()
print(next(g))
g.close()
print(list(g))
# çıktı: 1
# çıktı: finally çalıştı
# çıktı: []Generator içinde StopIteration yükseltmeyin
Eski kodlarda generator'ı erken bitirmek için raise StopIteration yazıldığını görebilirsiniz. Python 3.7'den beri PEP 479 tüm kodlar için varsayılan olarak etkindir: generator kodu doğrudan ya da dolaylı olarak StopIteration yükseltirse, bu istisna RuntimeError'a dönüştürülür ve orijinal istisna yeni hatanın nedeni (__cause__) olarak saklanır. Dolaylı durum özellikle sinsidir: generator içinde boş bir iterator üzerinde next() çağırmak da aynı sonucu doğurur.
def hatali():
it = iter([])
yield next(it)
try:
list(hatali())
except RuntimeError as e:
print(type(e).__name__ + ':', e)
print('nedeni:', type(e.__cause__).__name__)
# çıktı: RuntimeError: generator raised StopIteration
# çıktı: nedeni: StopIterationGenerator'ı bitirmek istediğinizde basitçe return yazın. İçeride next() çağırıyorsanız ya varsayılan değerli biçimi (next(it, None)) kullanın ya da StopIteration istisnasını yakalayıp return ile çıkın. İstisna hiyerarşisi ve yakalama kuralları için Python try except hata yönetimi yazısına bakabilirsiniz.
Generator'lar ve iş parçacıkları (Python 3.15)
Generator'lar aynı anda birden fazla iş parçacığından kullanılmak üzere tasarlanmamıştır. threading belgesine göre generator'ın bir yöntemi, generator zaten çalışırken başka bir yerden çağrılırsa ValueError yükselir; serbest iş parçacıklı (free-threaded) yapıya ilişkin belge de aynı iterator nesnesine birden fazla iş parçacığından erişmenin genel olarak güvenli olmadığını, öğelerin tekrarlanabileceğini ya da atlanabileceğini söyler.
Python 3.15 bu sorun için threading modülüne üç araç ekledi: serialize_iterator sarmalayıcısı, __next__() çağrılarını bir kilitle sıraya sokar ve her değeri tam olarak bir tüketiciye verir; synchronized_iterator bir generator fonksiyonunu dekore ederek ürettiği her generator'ı otomatik sarmalar; concurrent_tee() ise tek kaynaktan, her biri bütün değerleri gören birden çok iterator üretir. Aşağıdaki örnekte iki iş parçacığı aynı generator'ı paylaşıyor ve hiçbir değer tekrarlanmıyor.
import threading
def kareler(n):
for x in range(n):
yield x * x
kaynak = threading.serialize_iterator(kareler(6))
alinan = []
def tuket(iterable):
for oge in iterable:
alinan.append(oge)
t1 = threading.Thread(target=tuket, args=(kaynak,))
t2 = threading.Thread(target=tuket, args=(kaynak,))
t1.start(); t2.start()
t1.join(); t2.join()
print(sorted(alinan))
# çıktı: [0, 1, 4, 9, 16, 25]İş parçacıkları, kilitler ve havuzlar Python threading ve multiprocessing yazısında anlatılıyor. Asenkron generator'lar (async def içinde yield) ve async for ise Python asyncio, async ve await yazısının konusu.
Sık yapılan hatalar

- Generator'ı iki kez dolaşmak: İkinci dolaşma sessizce boş döner. Değerleri saklamanız gerekiyorsa
list()ile listeye çevirin. - len() ve indeks kullanmak: Generator'ın uzunluğu önceden bilinmez;
len(g)veg[0]aşağıdaki örnekte görüldüğü gibiTypeErrorverir. İlk öğe içinnext(g)kullanın. - Fonksiyonun çalıştığını sanmak:
uret()çağrısı gövdeyi çalıştırmaz. Generator'ı hiç tüketmezseniz içindeki kod, yan etkileri de dahil, hiç çalışmaz. - Generator'ı yazdırmak:
print(g)değerleri değil, nesnenin temsilini gösterir. Değerleri görmek içinlist(g)kullanın (ama bu generator'ı tüketir). - raise StopIteration yazmak: PEP 479 nedeniyle
RuntimeError'a dönüşür;returnkullanın. - Sonsuz generator'ı list() ile tüketmek: Program asla dönmez;
islice()veya birbreakkoşulu kullanın. - Comprehension içinde yield: Python 3.8'den beri comprehension ve generator ifadelerinin örtük kapsamında
yieldkullanmak yasaktır.
g = (x for x in range(3))
for ifade in ('len(g)', 'g[0]'):
try:
eval(ifade)
except TypeError as e:
print(type(e).__name__ + ':', e)
print(type(g).__name__, hasattr(g, '__next__'), hasattr([1], '__next__'))
# çıktı: TypeError: object of type 'generator' has no len()
# çıktı: TypeError: 'generator' object is not subscriptable
# çıktı: generator True FalseNe zaman generator, ne zaman liste?
Generator'lar her durumda listeden üstün değildir. Veriyi birden fazla kez dolaşacaksanız, uzunluğunu bilmeniz gerekiyorsa, rastgele erişim (indeks) yapacaksanız ya da veri zaten küçükse liste daha uygundur. Veri büyükse, dosya veya ağ gibi bir akıştan geliyorsa, sonsuzsa ya da hattın sonunda yalnızca bir toplam veya ilk eşleşme lazımsa generator daha uygundur. Aradaki seçimi yaparken ölçüt "bu değerleri kaç kez ve nasıl kullanacağım?" sorusudur.
Sık Sorulan Sorular
Generator ile iterator aynı şey mi?
Her generator bir iterator'dır, ama her iterator generator değildir. Generator, yield içeren bir fonksiyonun ya da bir generator ifadesinin ürettiği özel bir iterator türüdür; send(), throw() ve close() gibi ek yöntemleri vardır. Liste iterator'ı ya da sınıfla yazdığınız bir iterator da protokolü uygular ama bu ek yöntemlere sahip değildir.
Generator'ı başa sarabilir miyim?
Hayır. Iterator protokolü geri gitmeyi ya da sıfırlamayı tanımlamaz. Aynı değerleri yeniden istiyorsanız generator fonksiyonunu yeniden çağırın veya değerleri bir listeye kaydedin. Aynı akışı iki ayrı tüketiciye vermek için itertools.tee() de kullanılabilir; bu fonksiyon tüketilmeyen değerleri ara bellekte tutar.
Generator içinde return ne işe yarar?
return generator'ı bitirir. Bir değerle kullanılırsa (return sonuc), bu değer StopIteration.value olarak taşınır. for döngüsü bu değeri yok sayar; onu almanın pratik yolu sonuc = yield from alt_generator() yazımıdır.
Generator ifadesi mi, map() mi?
İkisi de tembel çalışır. map() var olan bir fonksiyonu uygulamak için kısa ve okunaklıdır; dönüşüm bir ifade ve koşul içeriyorsa generator ifadesi genellikle daha okunur. Ayrıntılar için Python lambda, map, filter ve sorted yazısına bakabilirsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · List comprehension · itertools ve functools · for ve while döngüleri · asyncio, async ve await · Python decorator yazma