8 - Python’ın İleri Seviye Özellikleri II – Iteratorlar, Üreteçler ve Tip İpuçları
2026
Önceki haftalarda Python’da özel metotları, nesne davranışlarını ve fonksiyonların da birer nesne gibi ele alınabildiğini gördük.
Bu hafta şu üç soruya odaklanıyoruz:
for döngüsü perde arkasında gerçekten ne yapıyor?Bugünkü başlıklar ilk bakışta farklı görünebilir. Ama aslında aynı fikre bağlanırlar:
Bazı durumlarda veriye ya da sonuca bir anda değil, adım adım ulaşırız.
Bu çerçevede şunları ayırt edebiliyor olmanızı hedefliyoruz:
iterable, iterator ve generator kavramlarınıfor yapısının iter() ve next() mantığıyla nasıl çalıştığınıyield kullanan üreteç arasındaki farkıBugünkü akış üç parçadan oluşuyor:
for döngüsü nasıl çalışır?iterable ve iterator ilişkisi nedir?yield ne yapar?Iterable[int] ile Iterator[int] arasında ne fark vardır?Ders boyunca şu soruya tekrar döneceğiz:
Veri elimde hazır mı, yoksa değerler tek tek mi geliyor?
for Döngüsü Perde Arkasında Ne Yapar?Bu örnek şunu gösterir:
iter(...) ile bir iterator alınırnext(...) ile tek tek istenirYani for döngüsünü anlamak için önce bu küçük mekanizmayı anlamamız gerekir.
Bir nesnenin for içinde çalışabilmesi için Python şu beklentiye sahiptir:
iter(nesne) çağrılabilmelinext(...) uygulanabilmeliAkış kabaca şöyledir:
for başlarken Python iter(...) çağırırnext(...) ile sıradaki elemanı isterStopIteration oluşurfor bu sinyali yakalar ve döngüyü bitirirBuradaki önemli nokta şudur: for sihirli bir yapı değildir. Belirli bir kurala göre çalışır.
for Döngüsünü Elle Kuralımmy_list = [10, 20, 30]
iterator_obj = iter(my_list)
print(f"Iterator tipi: {type(iterator_obj)}")
print(f"İlk eleman: {next(iterator_obj)}")
print(f"İkinci eleman: {next(iterator_obj)}")
print(f"Üçüncü eleman: {next(iterator_obj)}")
try:
print(next(iterator_obj))
except StopIteration:
print("Liste bitti, StopIteration alındı.")Bu örnek, for döngüsünün yaptığı işi daha görünür hale getirir:
Burada iki farklı kavram var:
Kısa örnek:
Burada:
my_list bir iterable’dırit ise bir iterator’dırYani liste veriyi tutar. Iterator ise o veriyi sırayla vermeyi sağlar.
class AdimliSayici:
def __init__(self, baslangic, son, adim):
if adim <= 0:
raise ValueError("Bu örnek için adim pozitif ve sifirdan buyuk olmali")
self.mevcut = baslangic
self.son = son
self.adim = adim
def __iter__(self):
return self
def __next__(self):
if self.mevcut >= self.son:
raise StopIteration
deger = self.mevcut
self.mevcut += self.adim
return deger
for sayi in AdimliSayici(0, 10, 2):
print(sayi)Bu örnekte nesnenin kendisi hem üzerinde dolaşılan yapı gibi hem de sıradaki elemanı veren nesne gibi davranıyor.
Not:
Az önceki sınıfta nesnenin kendisi hem veriyi tutan yapı gibi hem de sıradaki elemanı veren yapı gibi davrandı.
Bu yaklaşımın artıları:
Bu yaklaşımın eksileri:
Daha sonra istersek şu tasarımı da kurabiliriz:
__iter__() her çağrıldığında yeni bir iterator döndürürBöylece aynı veri üzerinde yeniden dolaşmak daha doğal hale gelir.
yield Nedir?yield, bir fonksiyonun değer üretip hemen tamamen bitmemesini sağlar.
Normalde bir fonksiyon return ile sonucu verip biter.
Ama yield kullanıldığında fonksiyon:
Bu yüzden yield kullanan fonksiyonlara çoğu zaman generator fonksiyonu denir.
yield ile Kurmakdef adim_say(baslangic, son, adim):
if adim <= 0:
raise ValueError("Bu örnek için adim pozitif ve sifirdan buyuk olmali")
mevcut = baslangic
while mevcut < son:
yield mevcut
mevcut += adim
for sayi in adim_say(0, 10, 2):
print(sayi)Burada sınıf yazmadan da sırayla değer üretebildik.
İlk örneğe göre fark şudur:
__iter__ ve __next__ metotlarını kendimiz yazmadıkyield Nasıl Düşünülmeli?Generator fonksiyonunu şu şekilde düşünebilirsiniz:
next() geldiğinde kod ilk yield noktasına kadar ilerlernext() çağrısında kaldığı yerden devam ederKısaca akış şöyledir:
Generator oluştur -> next() -> yield -> durakla -> next() -> devam et
Buradaki temel fark, fonksiyonun her değeri baştan üretmemesi; gerektiği anda üretmesidir.
yield Çalışırken Ne Olur?def basit_sayac_uretici(ust_limit):
print(">>> Fonksiyon başladı")
n = 0
while n < ust_limit:
print(f">>> yield {n} öncesi")
yield n
print(f">>> yield {n} sonrası")
n += 1
print(">>> Fonksiyon bitti")
generator_obj = basit_sayac_uretici(2)
print(next(generator_obj))
print(next(generator_obj))
try:
print(next(generator_obj))
except StopIteration:
print("Uretec bitti")Bu örnek, generator fonksiyonunun tek parça halinde değil, parça parça çalıştığını açıkça gösterir.
yield Kullanıyoruz?Bazı durumlarda tüm veriyi baştan üretmek istemeyiz:
Bu yaklaşım:
Bu yüzden bazen sonuçları baştan saklamak yerine, ihtiyaç oldukça üretmek daha doğru olur.
return ile yield Aynı Şey Değildirreturn |
yield |
|---|---|
| Fonksiyonu bitirir | Fonksiyonu duraklatır |
| Tek bir sonuç döndürür | Değerleri sırayla üretebilir |
| Kaldığı yeri korumaz | Kaldığı yeri korur |
Bu yüzden generator fonksiyonu, liste döndüren sıradan bir fonksiyonla aynı şey değildir.
def sayi_uret(n):
for i in range(n):
yield i
generator_obj = sayi_uret(3)
print(list(generator_obj))
print(list(generator_obj))Çıktı:
Çünkü generator’lar ve genel olarak iterator’lar çoğu zaman tek tek ilerleyen ve kullanıldıkça sona yaklaşan yapılar gibi davranır.
Aynı veriyi yeniden dolaşmak istiyorsanız:
| Kavram | Ne yapar? | Örnek |
|---|---|---|
Iterable |
Üzerinde dolaşılabilen veri kaynağıdır | list, str, dict, set, dosya |
Iterator |
Sıradaki elemanı tek tek verir | iter([10, 20, 30]) |
Generator |
yield ile değer üreten özel iterator’dır |
def sayac(): yield 1 |
Kritik noktalar:
Örneğin bir listeye doğrudan next() uygulayamazsınız; önce iter(...) ile iterator almanız gerekir.
İkisi de aynı kurala uyar. Fark daha çok yazım biçimi ve kullanım amacındadır.
Pratikte çoğu değer üretme işinde önce generator düşünmek iyi bir başlangıçtır.
Küçük bir not:
yield from yapısını görebilirsiniz(...)kareler_liste = [x * x for x in range(1000)]
kareler_uretec = (x * x for x in range(1000))
print(kareler_uretec)
for i in range(5):
print(next(kareler_uretec))Buradaki fark şudur:
[] kullanırsanız sonuçlar baştan listeye yazılır() kullanırsanız yalnızca bir generator nesnesi oluşurBu yapı, kısa ve okunaklı biçimde tembel üretim yapmayı sağlar.
Buraya kadar tip ipuçlarını özellikle kullanmadık.
Çünkü önce davranışı anlamak istedik.
Tip ipuçları, kodun hangi tür veriyle çalışmasının beklendiğini görünür hale getirir.
Ne sağlar?
Mypy gibi araçlarla hataları daha erken yakalamayı sağlarNe sağlamaz?
Yani tip ipucu, doğrudan davranışı değiştirmez; ama kodun sözleşmesini görünür kılar.
Bu hafta açısından en önemli ayrım şudur:
Iterable[int]: Üzerinde dolaşılabilen kaynakIterator[int]: Sıradaki int değerini veren nesneGenerator fonksiyonları da çoğu durumda Iterator[T] döndürür.
from typing import Iterable, Iterator
def toplam_hesapla(sayilar: Iterable[int]) -> int:
return sum(sayilar)
def harfleri_ver(kelime: str) -> Iterator[str]:
for harf in kelime:
yield harfBurada ilk fonksiyon, listeye özel davranmıyor; üzerinde dolaşılabilen herhangi bir kaynağı kabul ediyor.
Iterable, Ne Zaman Iterator?Bu ayrım pratikte önemlidir.
Iterable[T] kullanmak daha uygundur, eğer:
Iterator[T] kullanmak daha uygundur, eğer:
next(...) mantığı bu nesne üzerinde anlamlıysaYani çoğu durumda parametrede daha genel olan Iterable[T], dönüşte ise gerçekten sırayla değer üretiyorsanız Iterator[T] daha doğal bir seçimdir.
from typing import Iterable
def toplam_hesapla(sayilar: Iterable[int]) -> int:
return sum(sayilar)
liste_verisi = [1, 2, 3]
kume_verisi = {4, 5, 6}
uretec_verisi = (i for i in range(7, 10))
print(toplam_hesapla(liste_verisi))
print(toplam_hesapla(kume_verisi))
print(toplam_hesapla(uretec_verisi))Bu örnekte ortak nokta şudur:
list istemiyorIterable[int] ifadesi de tam olarak bunu anlatıyorKüçük bir not:
typing.Generator[...] gibi daha ayrıntılı tip gösterimleri de vardırBu imza şunu söyler:
int bekliyorint üretmeyi amaçlıyorAma Python yorumlayıcısı sırf tip ipucu var diye çağrıyı otomatik durdurmaz.
Bu tür hataları erken fark etmek için genelde şunlar kullanılır:
Mypyfrom typing import Iterable, Iterator
def cift_kareleri(sayilar: Iterable[int]) -> Iterator[int]:
for sayi in sayilar:
if sayi % 2 == 0:
yield sayi * sayi
rakamlar = [1, 2, 3, 4, 5, 6]
for kare in cift_kareleri(rakamlar):
print(kare)Burada dönüş tipi neden Iterator[int]?
int üreten bir yapı döndürüyorParametrede Iterable[int] kullanılması da önemlidir; çünkü bu fonksiyon yalnızca listeyle sınırlı değildir.
iterable, iterator ve generator farklı kavramlardırfor, arka planda iter() ve next() mantığıyla çalışıryield, fonksiyonun değer üretip kaldığı yerden devam etmesini sağlarBugünkü ortak fikir şuydu:
Her şeyi baştan üretmek zorunda değiliz; bazen doğru yaklaşım, adım adım ilerlemektir.
Aşağıdaki çalışmalarda üç şeyi birlikte düşünün:
İstenen davranış:
adim_say(baslangic, son, adim) fonksiyonu yield kullanmalıbaslangictan başlayıp son değerine gelmeden bitmelilist(adim_say(1, 10, 2)) -> [1, 3, 5, 7, 9]İstenen davranış:
İpucu:
index değerini artırıp aramaya devam edinStopIteration üretinÖrnek çıktı:
list(SesliHarfIterator("Merhaba Dünya")) -> ['e', 'a', 'a', 'ü', 'a']İstenen davranış:
Örnek çıktı:
[6, 11]Bir problemi çözerken şu üç soruyu düşünün:
Bu üç soru, çoğu zaman doğru aracı seçmenizi kolaylaştırır.