Python’da yield kelimesinin ne işe yaradığını anlamak için önce generatorlerin ne olduğunu anlamalıyız. Ve generatorleri anlamadan önce, yinelemeleri anlamalısınız. Ancak, bunları öğrenmeden önce yield’a hızlı bir bakış atalım.
return
komutunu öğrendiğinizi varsayıyoruz.
Bir benzetme olarak, return
ve yield
‘ı birbirinin ikizi gibi düşünebiliriz. return
‘döndür ve durdur’ anlamına gelirken , ‘yield
‘ ‘döndür, ancak devam et’ anlamına gelir:
- num_list’i
return
ile döndürmeye çalışalım
def num_list(n):
for i in range(n):
return i
Çıktı:
In [5]: num_list(3)
Out[5]: 0
Bir liste yerine sadece tek bir sayı döndürdük. return
benzer bir kodda asla mutlu bir şekilde ayrılmamıza izin vermiyor, sadece bir kez uygulanıyor ve bitiyor.
yield
‘a bakalım:
return
‘ü yield
ile değiştirelim
In [10]: def num_list(n):
...: for i in range(n):
...: yield i
...:
In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>
In [12]: list(num_list(3))
Out[12]: [0, 1, 2]
Şimdi tüm sayıları alabildik.
return
bir kez çalışır ve durur, ancak, yield
planladığınız zamanlarda çalışır. return
‘ü “bunlar arasından birini döndür” olarak yorumlayabiliriz. Diğer taraftan yield
‘ı “bunların hepsini döndür” olarak yorumlayabiliriz.
return
ifadesini yeniden aşağıdaki gibi yazalım
In [15]: def num_list(n):
...: result = []
...: for i in range(n):
...: result.append(i)
...: return result
In [16]: num_list(3)
Out[16]: [0, 1, 2]
Burada ise return bir liste döndürdü. Buna karşın yield kullandığımızda bir generator elde ettik.
Şimdi daha yield’a daha detaylı bakalım:
Yinelemeler nedir?
Bir liste oluşturduğunuzda öğelerini tek tek okuyabilirsiniz. Öğeleri tek tek okumak yineleme olarak adlandırılır:
>>> listem = [1, 2, 3]
>>> for i in listem:
... print(i)
1
2
3
listem
bir yinelenebilirdir. Bir list comprehension oluşturduğunuzda ise, hem bir liste oluşturursunuz ve hem de yinelenebilir:
>>> listem = [x*x for x in range(3)]
>>> for i in listem:
... print(i)
0
1
4
"for … in …"
şeklinde kullanabileceğiniz her şey yinelenebilirdir; listeler, dizeler, dosyalar …
Yineleme öğelerinden istediğimiz kadar kullanabiliriz. Dolayısıyla bunlar oldukça kullanışlılar. Ancak tüm değerleri bellekte saklarsak ve çok fazla değerimiz varsa kullanışlı olmayabilirler.
Generator nedir?
Generatorler yineleyicilerdir. Ancak bu yineleyiciler özel bir türdür ve yalnızca bir kez yinelenebilir. Generatorler değerleri bellekte saklamazlar ve kullanılacakları zaman üretirler (on-the-fly):
>>> generatorum = (x*x for x in range(3))
>>> for i in generatorum:
... print(i)
0
1
4
[]
yerine ()
kullanmamız dışında her şey aynı. AMA, generatorler sadece bir kez kullanılabildiklerinden, for i in generatorum
kalıbını ikinci kez kullanamayız: 0’ı hesaplarlar, sonra unuturlar. 1’i hesaplarlar, 4’ü hesaplarlar ve işlemi bitirirler.
yield nedir?
yield
, return
gibi kullanılan bir anahtar kelimedir. Ancak, yield kullanıldığında fonksiyon bir generator döndürür.
>>> def generator_olustur():
... listem = range(3)
... for i in listem:
... yield i*i
...
>>> generatorum = generator_olustur() # generator olusturalım
>>> print(generatorum) # generatorum bir nesne!
<generator object generator_olustur at 0xb7555c34>
>>> for i in generatorum:
... print(i)
0
1
4
Bu örnek çok kullanılışlı görünmeyebilir. Ancak fonksiyonunuzun yalnızca bir kez okumanız gereken çok fazla elemanlı bir değer kümesi döndüreceğini bildiğinizde kullanışlı olacaktır.
yield
konusunda ustalaşmak için fonksiyonu çağırdığınızda, fonksiyon gövdesine yazdığınız kodun çalışmadığını anlamalısınız. Fonksiyon yalnızca generator nesnesini döndürür. Bu kısım biraz karmaşık.
Kod bu şekilde generatorü her kullandığınızda kaldığı yerden devam edecektir.
Şimdi işin zor kısmı:
for
fonksiyonunuzda oluşturulan generator nesneyi ilk kez çağırdığında, fonksiyondaki kodu baştan yield
a ulaşana kadar çalıştırır. Ardından döngünün ilk değerini döndürür. Fonksiyona yapılan sonraki her çağrı fonksiyonda yazdığınız döngünün başka bir yinelemesini çalıştırır ve dolayısıyla bir sonraki değeri döndürür. Bu, generator boş kabul edilene kadar devam edecektir.
yield nasıl çalışır?
yield, yineleyici protokolü uygulamanın kolay bir yolunu sağlar. Bunu __iter__
ve __next__
(Python 2) veya __next__
(Python 3) metotları ile gerçekleştirir.
>>> def func():
... yield 'Ben'
... yield 'bir generatorum!'
...
>>> type(func) # yield olan bir fonksiyon yine de bir fonksiyondur
<type 'function'>
>>> gen = func()
>>> type(gen) # ama generator döndürür
<type 'generator'>
>>> hasattr(gen, '__iter__') # yinelenebilirdir
True
>>> hasattr(gen, 'next') # ve .next (.__next__ in Python 3) ile
True # iterator protokolünü çalıştırır
Sade bir dille anlatmak gerekirse;
Bir dizi sayı üzerinde işlem yapmak istiyorum, ancak bu dizinin yaratılmasıyla uğraşmak istemiyorum. Sadece yapmak istediğim işleme odaklanmak istiyorum. Bu yüzden aşağıdakileri yapıyorum:
Sizi arayıp belirli bir şekilde üretilmiş bir dizi sayı istediğimi söylüyorum ve algoritmanın ne olduğunu da söylüyorum.
Bu adım, generator fonksiyonunun, yani yield içeren fonksiyonun tanımlanmasına karşılık geldi.
Bir süre sonra sana “Tamam, bana sayıların sırasını anlatmaya hazır ol” diyorum.
Bu adım, bir generator nesnesi döndüren generator fonksiyonu çağırmaya karşılık geldi. Henüz bana bir sayı söylemediniz; sadece kağıt ve kaleminizi aldınız.
Şimdi size, “bana bir sonraki numarayı söyleyin” diyorum ve siz bana ilk numarayı söylüyorsunuz. Ondan sonra, benden bir sonraki numarayı sormamı bekliyorsunuz. Nerede olduğunuzu, hangi sayıları söylediğinizi ve bir sonraki sayının ne olduğunu hatırlamak sizin işiniz. Detaylar benim için önemli değil.
Bu adım, generator nesnesinde .next()
‘i çağırmaya karşılık geldi.
Buraya kadar önceki adımları tekrar ettik ve sona geldik. Burada artık bana “başka sayı yok!” diyorsunuz.
Bu adım, generator nesnenin işini bitirmesine ve bir StopIteration istisnasının fırlatılmasına karşılık geldi. Generator fonksiyonunun istisnayı oluşturması gerekmez. Fonksiyon sona erdiğinde veya bir dönüt verdiğinde otomatik olarak fırlatılır.
Dolayısıyla generatorun yaptığı şey şöyledir; yürütmeye başlar, bir ürün verdiğinde duraklar ve bir .next()
değeri sorulduğunda son noktadan devam eder. Python’un yineleyici protokolü ile de tasarımı gereği mükemmel uyum sağlar.
>>> def f():
... yield 1
... yield 2
... yield 3
...
>>> g = f()
>>> for i in g:
... print(i)
...
1
2
3
>>> for i in g:
... print(i)
...
>>> # Artık yazdıracak bir şey kalmadı