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ığındaStopIterationfı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
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.
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.
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.