Generators, iterator yaratmanın basit bir yoludur. Bir generator, iteratif olabilecek bir nesneyi döndüren fonksiyondur. Basit örneklerle konuyu genişletelim.
13.2.1. Bir generator yaratma
Python’da en kolay şekilde bir generator oluşturma return yerine yield ifadesini kullanmaktır. Eğer bir fonksiyon yield ifadesi içeriyorsa bir generator fonksiyonu haline dönüşür. Aslında bir fonksiyonda yield ve return ifadesi aynı değeri döndürür. Ancak, return ifadesi bir fonksiyonu sonlandırırken, yield ifadesi değeri döndürür, saklar ve fonksiyonu çağırma devam eder.
Bir örnek yapmadan önce normal bir fonksiyona göre farklılıkları sıralayalım:
- Bir generator fonksiyonu bir veya birden fazla yield ifadesi içerebilir.
- Çağrıldığı zaman, bir iterator nesnesi döndürür, ama işlemi hemen çalıştırma başlamaz.
- __iter__() ve __next__() metotları otomatik olarak uygulanabilir.
- Yield işlemi çağrıldığında kontrol duraklar ve çağrıya aktarılır.
- Yerel değişkenler ve durumları ardışık çağrılarda unutulmaz.
- Fonksiyon bittiğinde, StopIteration durumu tetiklenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# Basit bir generator def my_gen(): i = 1 print('Birinci') yield i i += 1 print('İkinci') yield i i += 1 print('Üçüncü') yield i a = my_gen() print(next(a)) print(next(a)) print(next(a)) |
1 2 3 4 5 6 |
Birinci 1 İkinci 2 Üçüncü 3 |
Örneği inceleyip, yukarıdaki maddeleri tekrar okuyalım. i değeri her çağırma işleminde (next) yield bölümüne kadar fonksiyon çalışır. Burada duraklar ve yeni bir çağırma bekler. Yeni çağırmada tekrar çalışır. Durum kalmayıncaya kadar işlem devam eder. Ve durum kalmayınca bir StopIteration oluşur. Aynı işlemi for döngüsü ile yaparsak:
1 2 |
for oge in my_gen(): print(oge) |
Çıktı olarak yukarıdaki çıktının aynısı oluşur.
13.2.2. Döngü içeren generator
Şimdi yukarıdaki yield işlemini döngü ile tasarlayacağız. Bir string değişkenin tersini alan bir fonksiyon tasarlayalım.
1 2 3 4 5 6 7 8 |
def rev_str(my_str): 'Döngülü yield' length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] for char in rev_str("merhaba"): print(char) |
1 2 3 4 5 6 7 |
a b a h r e m |
range fonksiyonu en üst öğeden başlar ve -1 oluncaya kadar -1 azalır.
Bu fonksiyon sadece string için değil hem list hem de tuple için çalışır.
13.2.3. Generator ifadesi
Generator ifadesi kullanarak genaratorler oluşturulabilir. Lambda ile isimsiz fonksiyonlar oluşturabildiği gibi generator ifadesi ile anonim bir generator fonksiyonu oluşturur. Generator sözdizimi list ifadesinin sözdizimine çok benzer, ama köşeli parantez yerine normal parantezler kullanılır. Bir list ifadesi ile bir generator ifadesi arasındaki hem önemli fark bir list ifadesi tam bir liste oluştururken genarator ifadesi bir seferde bir öğe oluşturur. Bir generator ifadesi sadece istenildiğinde öğeyi oluşturur. Bu sebepten dolayı bir generator ifadesinin list ifadesine göre belleği çok daha etkin kullandığı söylenebilir.
1 2 3 4 5 6 7 8 9 10 |
# Bir liste my_list = [1, 3, 6, 10] # Bir liste ifadesi, döngü ile oluşturulmuş # Çıktısı: [1, 9, 36, 100] [x**2 for x in my_list] # generator ifadesi, parantezlere dikkat, Çıktısına dikkat # Çıktısı: <generator object <genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list) |
Bu generator ifadesinin öğelerine ulaşmak için next metodu kullanılır. Örneğin,
1 2 3 4 5 6 7 |
a = (x**2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) # StopIteration next(a) |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
1 9 36 100 --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-24-9f36b9be9419> in <module>() 5 print(next(a)) 6 # StopIteration ----> 7 next(a) StopIteration: |
StopIteration exception’ını oluştu.
Generator ile max, min, sum ve vb. fonksiyonlarda kullanılabilir. Örneğin,
1 2 |
print(sum(x**2 for x in my_list)) print(max(x**2 for x in my_list)) |
1 2 |
146 100 |
13.2.4. Generator niye kullanılır?
4 alt başlıkta bu konuyu işleyelim.
13.2.4.1. Uygulaması kolay
Generators iterator sınıfında olduğu gibi uygulaması kesin ve açıktır. Öncelikle iterator bölümünde anlattığımız küp alma sınıfını hatırlayalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class KupAl: 'Belirtilen sayıya kadar küp hesaplayan sınıf' def __init__(self, bitis = 0): self.bitis = bitis def __iter__(self): self.n = 0 return self def __next__(self): if self.n <= self.bitis: result = 2 ** self.n self.n += 1 return result else: raise StopIteration |
Biraz uzun bir oldu, şimdi aynı işlemi generator kullanarak yapalım.
1 2 3 4 5 6 7 |
def KupAlGen(i = 0): n = 0 while n < i: yield 2 ** n n += 1 print(KupAlGen(5)) |
1 |
<generator object KupAlGen at 0x00000170531D1E58> |
Örnekte olduğu generators uygulanması çok daha kolay ve daha az kodla yapılabilir. next() metodu ile yukarıdaki örnek dolaşabilir.
13.2.4.2. Bellek etkinliği
Bir liste döndürmek için normal bir fonksiyon, sonucu döndürmeden önce belleğe tüm diziyi oluşturur. Eğer liste çok büyükse bu diziyi tamamen belleğe yüklemek belleği aşırı şekilde kullanılmasına neden olabilir. Generator ise bellek dostu olup bir seferde sadece bir öğe ürettiği için tercih edilir.
13.2.4.3. Sonsuz bir veri akışı sunar
Generator, sonsuz bir veri akışını temsil etmek için mükemmel bir ortamdır. Sonsuz akışlar bellekte saklanamaz ve bir generator bir seferde yalnızca bir öğe ürettiğinden, sonsuz veri akışını temsil edebilir. Aşağıdaki örnek tüm çift sayıları oluşturabilir (en azından teoride, sonuçta adım adım ulaştığımızı unutmayalım).
1 2 3 4 5 |
def all_even(): n = 0 while True: yield n n += 2 |
13.2.4.4. Generator ile pipeline
Generator, bir dizi işlemi sıraya koyup işelemek (pipeline) için kullanılabilir. Örneğin, bir restoranın bir log dosyasına sahip olalım. Bu dosya, her saatte satılan pizza sayısını 2. kolonda tutsun ve 5 yıl içinde toplam pizza sayısını bulmak için aşağıdaki gibi bir generator uygulaması yazılabilir.
1 2 3 4 |
with open('satis.log') as file: pizza_kolonu = (line[1] for line in file) saatlik_satis = (int(x) for x in pizza_kolonu) print("Toplam pizza sayısı = ",sum(saaatlik_satis)) |
Bu pipeline işlemi hazırlaması kolay ve etkindir.