2.31. Yineleyiciler ve generator’lar

for döngüsü, göründüğünden daha fazla iş yapmaktadır. Bu sayfa, üzerinde çalıştığı yineleyici protokolünü ve kendi yineleyicilerinizi oluşturmanızı sağlayan yield anahtar kelimesini ele alır.

2.31.1. Yineleyici protokolü

Üzerinde döngü kurulabilen her nesne iki metot uygular:

  • __iter__() – nesnenin öğeleri üzerinde bir yineleyici döndürür.

  • __next__() – yineleyici üzerinde, bir sonraki öğeyi döndürür veya başka öğe kalmadığında StopIteration fırlatır.

iter() yerleşiği __iter__‘i çağırır; next() __next__‘i çağırır. Bir listede elle adım adım ilerleyin:

it = iter([10, 20, 30])
print(next(it))    # 10
print(next(it))    # 20
print(next(it))    # 30
print(next(it))    # raises StopIteration
Bir for döngüsü, bir yineleyici elde etmek için __iter__'i bir kez çağırır, ardından StopIteration döngüyü sonlandırana kadar __next__'i tekrar tekrar çağırır.

for, “__iter__‘i bir kez çağır, ardından StopIteration‘a kadar __next__ üzerinde döngü kur” işleminin şeker sözdizimidir.

for x in items: ifadesinin gerçekte yaptığı şey:

_it = iter(items)
while True:
    try:
        x = next(_it)
    except StopIteration:
        break
    # ... loop body ...

Her liste, demet, dize, sözlük, küme, dosya nesnesi ve generator zaten __iter__ ve __next__‘i uygular – bu yüzden hepsi for ile çalışır.

2.31.2. yield ve generator fonksiyonları

Bir yield deyimi içeren bir fonksiyon, bir generator fonksiyonudur. Onu çağırmak gövdeyi çalıştırmaz; gövdeyi her seferinde bir yield olacak şekilde çalıştıran bir generator nesnesi (bir yineleyici) döndürür:

def count_up_to(n):
    i = 0
    while i < n:
        yield i
        i += 1

for value in count_up_to(3):
    print(value)

Çıktı:

0
1
2

next()‘in her çağrısı, fonksiyonu bir sonraki yield‘e kadar devam ettirir, o değeri çağırana verir ve orada duraklar. Yerel durum (bu örnekte i) devam ettirmeler arasında korunur.

İki yaşam çizgisi olan dizi diyagramı (çağıran ve generator gövdesi). Çağıran, gövdeyi çalıştırmadan bir generator oluşturan count_up_to(3)'ü çağırır. Sonraki her next(), gövdeyi bir sonraki yield'e kadar çalıştırır, verilen değeri döndürür ve duraklar. Dördüncü next() sonun ötesine düşer ve StopIteration fırlatır. i değişkeni duraklamalar arasında korunur.

next(), gövdeyi bir sonraki yield‘e kadar çalıştırır, değeri geri verir ve duraklar. Yerel durum duraklamayı atlatır.

Generator’lar, bir diziyi tembel şekilde üretmenin en kolay yoludur – hiçbir liste oluşturulmaz, öğeler yalnızca tüketici istediğinde hesaplanır ve fonksiyon isterse sonsuza kadar öğe üretebilir.

2.31.3. Tembel hatlar (pipeline’lar)

Generator’lar iyi birleşir. Bir generator’ın çıktısı bir başkasını besleyebilir:

def numbers():
    i = 0
    while True:
        yield i
        i += 1

def squares(source):
    for x in source:
        yield x * x

pipeline = squares(numbers())

for v in pipeline:
    if v > 100:
        break
    print(v)

Değerler, hat boyunca her seferinde bir tane akar – ara liste yok, numbers‘a yerleşik bir üst sınır yok ve durmaya tüketici (for v in pipeline) karar verir.

Soldan sağa üç kutu: numbers(), squares(source) ve for-v-in-pipeline tüketicisi. Altta üç döngü çizilmiştir. Her döngüde tüketici, squares'a sola doğru bir çekme isteği gönderir; o da numbers'a sola doğru bir çekme gönderir; numbers, squares'a sağa doğru bir değer üretir; o da kare alınmış değerini sağa doğru tüketiciye üretir.

Tüketici üzerindeki her next(), zincir boyunca bir çekme tetikler; değerler yalnızca bir şey istediğinde var olur.

2.31.3.1. yield from

Başka bir yinelenebilirden öğe çeken ve her birini üreten bir döngü, Python’ın bir kısayol sağlayacak kadar yaygındır. yield from iter ifadesi, yinelenebilirin ürettiği her değeri sırayla üretir – sanki generator satır içinde bir for x in iter: yield x döngüsüne sahipmiş gibi:

def chain(*sources):
    for source in sources:
        yield from source

for v in chain([1, 2, 3], (4, 5), "abc"):
    print(v)

Çıktı:

1
2
3
4
5
a
b
c

yield from, açık for döngüsüne tam olarak eşdeğerdir, yalnızca daha kısadır ve StopIteration‘ı iç yinelenebilirden dış generator’a temiz bir şekilde iletir – birkaç generator’ı uçtan uca zincirlerken kullanışlıdır.

2.31.3.2. yield tükendiğinde

Bir generator fonksiyonunun sonundan düşmek (veya açık bir return‘e çarpmak) otomatik olarak StopIteration fırlatır. Onu elle fırlatmaya gerek yoktur; çevreleyen for döngüsü onu görür ve sona erer.

Üreten kod doğal olarak birkaç yield noktası olan bir döngü şeklinde yazıldığında generator’ları kullanın; tüm diziye gerçekten bellekte ihtiyacınız olduğunda ise düz bir liste anlamlandırması (list comprehension) kullanın.