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
A for loop calls __iter__ once to get an iterator, then calls __next__ repeatedly until StopIteration ends the loop.

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.

Sequence diagram with two lifelines (caller and generator body). The caller calls count_up_to(3), which creates a generator without running the body. Each subsequent next() runs the body until the next yield, returns the yielded value, and pauses. The fourth next() falls off the end and raises StopIteration. The variable i is preserved across pauses.

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.

Three boxes left-to-right: numbers(), squares(source), and the for-v-in-pipeline consumer. Three cycles are drawn beneath. In each cycle, the consumer sends a pull request leftward to squares, which sends a pull leftward to numbers; numbers yields a value rightward to squares, which yields its squared value rightward to the consumer.

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.