8 - Python’ın İleri Seviye Özellikleri II – Iteratorlar, Üreteçler ve Tip İpuçları
2025
for döngüsünün iç yüzü.__iter__, __next__)yield ile sihirli duraklatma. 🏭(...).for döngüsü Python’da en sık kullandığımız yapılardan biri. Bir listedeki, string’deki veya başka bir koleksiyondaki elemanları kolayca gezmemizi sağlar.
Peki, Python bu for item in my_list ifadesini nasıl anlıyor ve arka planda neler oluyor? İşte burada Iterator Protokolü devreye giriyor.
__iter__ metoduna sahip olmalıdırlar.__iter__ (genellikle kendini döndürür) ve __next__ metodlarına sahiptir.for Döngüsünün Çalışma Adımları:
for döngüsü başlarken, iter(my_list) çağrılır. Bu, listeden bir iterator (yer imi) nesnesi alır.next() metodu çağrılır. next(), sıradaki elemanı (kitabın o anki sayfasını) döndürür ve yer imini bir sonraki elemana ilerletir.next() metodu özel bir sinyal olan StopIteration hatasını fırlatır.for döngüsü bu StopIteration sinyalini yakalar ve döngüyü sonlandırır.for döngüsünün yaptığını elle yapalım:
my_list = [10, 20, 30]
# 1. Iterable'dan iterator al (Yer imini oluştur)
iterator_obj = iter(my_list)
print(f"Iterator tipi: {type(iterator_obj)}")
# 2. Sıradaki elemanı iste (Yer iminden oku ve ilerlet)
print(f"İlk eleman: {next(iterator_obj)}") # 10
print(f"İkinci eleman: {next(iterator_obj)}") # 20
print(f"Üçüncü eleman: {next(iterator_obj)}") # 30
# 3. Başka eleman kalmadı mı? (Kitap bitti mi?)
try:
print(next(iterator_obj))
except StopIteration:
print("Liste bitti, StopIteration alındı.")Bu protokol, Python’da farklı veri yapılarında tutarlı bir şekilde gezinmeyi sağlar.
Kendi veri yapılarımız veya özel sıralı erişim mantığımız için iterator oluşturabiliriz. Örneğin, belirli bir adıma göre artan sayılar üreten bir iterator:
class AdimliSayici:
def __init__(self, baslangic, son, adim):
self.mevcut = baslangic
self.son = son
self.adim = adim
def __iter__(self): # Iterator protokolü: __iter__ kendini döndürmeli
# Neden self? Çünkü bu nesnenin kendisi zaten bir iterator.
# iter(adimli_sayici_nesnesi) çağrıldığında, tekrar kendisini vermeli.
return self
def __next__(self): # Iterator protokolü: __next__ sıradaki değeri vermeli
if self.mevcut >= self.son:
raise StopIteration # Sona ulaşıldı, sinyali ver
deger = self.mevcut
self.mevcut += self.adim # Bir sonraki adıma geç
return deger
# Kullanım:
for sayi in AdimliSayici(0, 10, 2): # 0, 2, 4, 6, 8
print(sayi)__iter__ ve __next__ metodlarını doğru şekilde implemente etmek yeterlidir. Bir nesne iterator ise, __iter__ genellikle self döndürür.
Ya işlememiz gereken veri çok büyükse? Milyonlarca satırlık bir dosya, sensörlerden gelen sürekli veri akışı, devasa bir veritabanı sorgusu sonucu…
# Milyarlarca sayının karesini hesaplamak istiyoruz diyelim
# numbers = list(range(1_000_000_000)) # Bu satır bilgisayarı kilitleyebilir! MemoryError!
# squares = []
# for n in numbers:
# squares.append(n*n) # squares listesi de devasa olur!Tüm veriyi aynı anda bir listeye veya başka bir yapıya yüklemek, hem çok fazla bellek (RAM) tüketir hem de çok uzun sürebilir. Bazen bu mümkün bile olmaz.
Çözüm: Veriyi parça parça, sadece ihtiyaç duyulduğu anda işlemek.
Üreteç Nedir? Üreteçler, elemanları tek tek ve sadece istendiğinde üreten özel tür iteratorlardır. Normal bir fonksiyon gibi tanımlanırlar ama return yerine yield anahtar kelimesini kullanırlar.
Neden İhtiyaç Duyuldu? Büyük veri setleriyle çalışırken belleği verimli kullanmak için icat edildiler. Tüm sonuçları bir listede biriktirmek yerine, her sonucu ihtiyaç anında “üretip” verirler.
yield’ın Sihri: yield, fonksiyonun çalışmasını duraklatır, bir değer döndürür ve fonksiyonun o anki durumunu (lokal değişkenler, nerede kaldığı) hafızasında tutar. Fonksiyondan bir sonraki değer istendiğinde, kaldığı yerden çalışmaya devam eder.
yield Nasıl Çalışır? Adım Adım İzleyelimyield içeren bir fonksiyon (üreteç fonksiyonu) çağırdığımızda ne olur?
def basit_sayac_uretici(ust_limit):
print(">>> Üreteç fonksiyonu çağrıldı, kod BAŞLIYOR...")
n = 0
while n < ust_limit:
print(f">>> yield {n} öncesi")
yield n # DURAKLAMA NOKTASI! Değeri ver ve bekle.
print(f">>> yield {n} sonrası (kaldığı yerden devam)")
n += 1
print(">>> Üreteç fonksiyonu bitti.")
generator_obj = basit_sayac_uretici(2)
print("--- Üreteç nesnesi oluşturuldu (kod henüz çalışmadı!) ---")
print(f"Nesne: {generator_obj}")
print("\n--- İlk next() ---")
sonuc1 = next(generator_obj) # Kod yield n'e kadar çalışır (n=0)
print(f"--- Dönen Değer: {sonuc1} ---") # 0 döner, fonksiyon yield'da duraklar.
print("\n--- İkinci next() ---")
sonuc2 = next(generator_obj) # Kod yield'dan sonra devam eder, döngü döner, yield n'e gelir (n=1)
print(f"--- Dönen Değer: {sonuc2} ---") # 1 döner, fonksiyon yield'da tekrar duraklar.
print("\n--- Üçüncü next() ---")
try:
next(generator_obj) # Kod yield'dan sonra devam eder, döngü biter, fonksiyon sonlanır.
except StopIteration:
print("--- StopIteration alındı (üreteç bitti) ---")Benzetme: yield’ı bir filmdeki “pause” (duraklat) düğmesi gibi düşünün. Filmi (fonksiyonu) başlatırsınız, yield’a gelince durur ve o anki sahneyi (değeri) size verir. Tekrar “play” (next()) dediğinizde kaldığı yerden devam eder.
for döngüsü, list(), sum() gibi bir yapıyla tüketildikten sonra boşalır.def sayi_uret(n):
for i in range(n):
yield i
generator_obj = sayi_uret(3)
print("İlk tüketim (liste oluşturma):")
liste1 = list(generator_obj) # Üreteç tüketilir
print(liste1) # Çıktı: [0, 1, 2]
print("\nİkinci tüketim denemesi:")
liste2 = list(generator_obj) # Üreteç zaten boş!
print(liste2) # Çıktı: []
# Eğer tekrar kullanmak isterseniz, üreteci yeniden oluşturmalısınız:
generator_yeni = sayi_uret(3)
print("\nYeni üreteçle tüketim:")
for sayi in generator_yeni:
print(sayi) # Çıktı: 0, 1, 2Bu, bellek verimliliğinin bir sonucudur. Üreteç, tüm değerleri saklamadığı için, tüketildikten sonra başa dönemez.
(...)Liste comprehensions ([]) kullanarak anında liste oluşturabildiğimiz gibi, parantez (()) kullanarak da anında üreteç nesnesi oluşturabiliriz. Bu, basit dönüşümler için çok pratik bir kısa yoldur.
# Liste Comprehension (Bellekte tam liste oluşturur)
kareler_liste = [x * x for x in range(1000)]
# print(f"Liste boyutu (yaklaşık): {kareler_liste.__sizeof__()} bytes") # Bellek kullanımı artar
# Üreteç İfadesi (Sadece üreteç nesnesi oluşturur, değerler üretilmez)
kareler_uretec = (x * x for x in range(1000))
# print(f"Üreteç boyutu (yaklaşık): {kareler_uretec.__sizeof__()} bytes") # Çok daha küçük!
print(f"Üreteç nesnesi: {kareler_uretec}")
# Değerler sadece döngüde veya next() ile istendiğinde üretilir:
print("İlk 5 kare (üreteçten):")
for i in range(5):
print(next(kareler_uretec)) # 0, 1, 4, 9, 16
# Veya tümünü tüketmek için:
# toplam = sum(kareler_uretec) # Kalan karelerin toplamını verimli hesaplarÜreteç ifadeleri, “lazy evaluation” (tembel değerlendirme) prensibini kısa ve okunaklı bir şekilde uygular.
Nedir? Kodun farklı bölümlerinin (değişkenler, fonksiyon parametreleri, dönüş değerleri) ne tür verilerle çalışması beklendiğini belirten standartlaşmış bir notasyon sistemidir.
Ne Değildir? Tip ipuçları, Python’ı Java veya C# gibi statik tipli bir dile dönüştürmez. Yorumlayıcı, çalışma zamanında tipleri zorunlu kılmaz (ek araçlar kullanmadıkça).
Neden Kullanılır?
MyPy gibi statik analiz araçları, kod çalışmadan tip uyumsuzluklarını yakalar.Temel tipler (int, str, float, bool, list, dict vb.) doğrudan kullanılabilir. Daha karmaşık durumlar için typing modülü devreye girer.
from typing import List, Dict, Tuple, Optional, Union, Any, Iterable, Iterator
# Temel tipler
ogrenci_no: int = 123
ders_adi: str = "NTP 2"
# Koleksiyon tipleri (içerik tipiyle belirtilir)
notlar: List[int] = [80, 90, 75]
ogrenci_bilgileri: Dict[str, Any] = {"ad": "Ayşe", "no": 123, "aktif": True}
# None içerebilen tipler için Optional veya Union
aciklama: Optional[str] = None # Union[str, None] ile aynı
# Birden fazla olası tip için Union
kimlik: Union[int, str] = "ABC-123"
# Fonksiyon imzaları
def kayit_olustur(isim: str, yas: int) -> Dict[str, Any]:
"""Yeni bir kayıt oluşturur ve sözlük olarak döndürür."""
# ... kod ...
return {"ad": isim, "yas": yas}
# Iterable (üzerinden geçilebilir) ve Iterator (tek tek veren) tipleri
def harfleri_ver(kelime: str) -> Iterator[str]:
"""Kelimenin harflerini tek tek yield eder."""
for harf in kelime:
yield harftyping modülü, Python’un tip sistemini ifade etmek için zengin araçlar sunar.
Bir listedeki çift sayıların karelerini yield eden, tip ipuçları kullanılmış bir üreteç fonksiyonu yazalım.
from typing import List, Iterator
def cift_kareleri(sayilar: List[int]) -> Iterator[int]:
"""Verilen listedeki çift sayıların karelerini yield eder."""
print("Çift kareleri üreteci çalışıyor...")
for sayi in sayilar:
if sayi % 2 == 0:
kare = sayi * sayi
print(f" -> {sayi} çift, karesi {kare} yield ediliyor.")
yield kare
else:
print(f" -> {sayi} tek, atlanıyor.")
# Kullanım
rakamlar = [1, 2, 3, 4, 5, 6]
kare_uretici = cift_kareleri(rakamlar)
print("İlk çift karenin alınması:")
print(f"Sonuç: {next(kare_uretici)}") # 2'nin karesi 4
print("\nKalan kareler için döngü:")
for k in kare_uretici: # Kaldığı yerden devam eder (4 ve 6 için)
print(f"Sonuç: {k}")Bir cümlenin kelimeleri üzerinde gezinen, tip ipuçları kullanılmış özel bir iterator sınıfı oluşturalım.
from typing import List, Iterator
class KelimeIterator:
def __init__(self, cumle: str) -> None:
self.kelimeler: List[str] = cumle.split() # Cümleyi kelimelere ayır
self._index: int = 0 # Mevcut kelimenin indeksi
def __iter__(self) -> Iterator[str]: # Kendisi iterator, kendini döndür
return self
def __next__(self) -> str: # Sıradaki kelimeyi döndür
if self._index < len(self.kelimeler):
kelime = self.kelimeler[self._index]
self._index += 1
return kelime
else:
raise StopIteration # Kelimeler bitti
# Kullanım
metin = "Bu basit bir cümledir"
kelime_gezici = KelimeIterator(metin)
print("Cümlenin kelimeleri:")
for k in kelime_gezici:
print(k)
# Çıktı:
# Bu
# basit
# bir
# cümledir__iter__, __next__): Python’da koleksiyonlar üzerinde tutarlı gezinmenin temelidir. for döngüsünün arkasındaki sihirbazdır. (Iterator’lar __iter__ ile genelde self döndürür).yield): Bellek verimliliği şampiyonları! Değerleri ihtiyaç anında üretirler, büyük veri setleri veya sonsuz diziler için idealdirler. Fonksiyonun durumunu koruyarak duraklatıp devam etme yeteneği sunarlar. Dikkat: Sadece bir kez tüketilebilirler!(...): Basit üreteçleri tek satırda, okunaklı bir şekilde oluşturmanın kısa yoludur. (Bunlar da tek kullanımlıktır).Bu araçları anlamak ve doğru yerde kullanmak, daha verimli, okunaklı ve sağlam Python programları yazmanızı sağlar.
Aşağıdaki problemleri Python’da çözmeye çalışın (Tip ipuçlarını kullanmayı unutmayın!):
adim_say(baslangic: int, son: int, adim: int) -> Iterator[int] adında bir üreteç fonksiyonu yazın.baslangic’tan başlayıp son’a kadar (son dahil değil) adim kadar artarak sayıları yield etmeli.list(adim_say(1, 10, 2)) çıktısı [1, 3, 5, 7, 9] olmalı.SesliHarfIterator adında bir sınıf oluşturun.__init__ metodu bir string (metin: str) alsın.__iter__ ve __next__ metodlarını uygun şekilde implemente edin.list(SesliHarfIterator("Merhaba Dünya")) çıktısı ['e', 'a', 'a', 'ü', 'a'] (veya benzeri) olmalı.kelimeler = ["Python", "programlama", "çok", "güzel", "bir", "dil"] listesi verilsin.for döngüsü ile dolaşıp uzunlukları ekrana yazdırın.