Python yield Anahtar Sözcüğü: Nedir ve Nasıl Kullanılır?
Python’daki yield anahtar sözcüğü, normal bir fonksiyonu, tüm değerleri bir kerede hesaplamak yerine talep üzerine değerler üreten bir jeneratöre dönüştürür.
Python fonksiyonlarının her zaman bir return ifadesi olmak zorunda değildir. Jeneratör fonksiyonları, return yerine yield anahtar sözcüğünü içeren fonksiyonlardır.
Bu fonksiyonlar bir veri akışını temsil eden jeneratör yineleyiciler üretir. Bir yineleyici tarafından temsil edilen öğeler yalnızca gerektiğinde oluşturulur ve verilir. Bu tür değerlendirme genellikle tembel değerlendirme (lazy evaluation) olarak anılır.
Büyük veri kümeleriyle çalışırken, jeneratörler, öğelerinin her biri için bellekte yer gerektiren listeler, demetler ve diğer veri yapılarında veriyi saklamaya kıyasla bellek açısından verimli bir alternatif sunar. Jeneratör fonksiyonları ayrıca, listeler ve demetler gibi hevesli (eager) değerlendirilen yapılarla mümkün olmayan sonsuz yineleyiciler de oluşturabilir.
Başlamadan önce, fonksiyonlar ile jeneratörler arasındaki farkları kısaca hatırlayalım:
Özellik
Fonksiyon
Jeneratör
Değer Üretimi
Tüm değerleri bir kerede döndürür
Değerleri talep üzerine, teker teker verir
Yürütme
Döndürmeden önce tamamen çalışır
Değer verdikten sonra duraklar, bir sonraki değer istendiğinde devam eder
Anahtar Sözcük
return
yield
Bellek Kullanımı
Potansiyel olarak yüksek; tüm diziyi bellekte tutar
Düşük; yalnızca geçerli değeri ve bir sonraki için durumu tutar
Yineleme
Birden çok yineleme mümkün, ancak tüm dizinin saklanmasını gerektirir
Tek geçişli yineleme için tasarlanmıştır; büyük veya sonsuz dizilerde daha verimlidir
Python'un yield Anahtar Sözcüğünü Kullanarak Jeneratör Fonksiyonları Oluşturma
Python’da jeneratör terimi, bir jeneratör yineleyiciyi veya bir jeneratör fonksiyonunu ifade edebilir. Bunlar Python’da farklı fakat ilişkili nesnelerdir. Bu eğitimde karışıklığı önlemek için tam terimler sıkça kullanılmaktadır.
Önce jeneratör fonksiyonlarını inceleyelim. Bir jeneratör fonksiyonu, normal bir fonksiyona benzer görünür ancak return yerine yield anahtar sözcüğünü içerir.
Bir Python programı bir jeneratör fonksiyonunu çağırdığında, bir jeneratör yineleyici oluşturur. Yineleyiciler talep üzerine bir değer verir ve başka bir değer gerektiğinde yürütmeyi duraklatır. Bu kavramı açıklamak ve normal fonksiyonlarla jeneratör fonksiyonları arasındaki farkı göstermek için bir örneğe bakalım.
Normal bir fonksiyon kullanma
Önce, bir return ifadesi içeren normal bir fonksiyon tanımlayalım. Bu fonksiyon bir kelime dizisi ve bir harf alır ve her kelimedeki söz konusu harfin kaç kez geçtiğini içeren bir liste döndürür:
deffind_letter_occurrences(words, letter):
output =[]for word in words:
output.append(word.count(letter))return output
print(
find_letter_occurrences(["apple","banana","cherry"],"a"))Altyapı:
Fonksiyon, listedeki 1, 3 ve 0 değerlerini çıktılar çünkü apple’da bir, banana’da üç ve cherry’de hiç a yok. Aynı fonksiyon, boş bir liste başlatıp .append() kullanmak yerine liste üreteçleriyle (list comprehension) yeniden yazılabilir:
deffind_letter_occurrences(words, letter):return[word.count(letter)for word in words]Altyapı:
Bu normal fonksiyon her çağrıldığında tüm sonuçları içeren bir liste döndürür. Ancak kelime listesi büyükse, bu normal fonksiyonu çağırmak bellek üzerinde baskı oluşturur; çünkü program, orijinaliyle aynı boyutta yeni bir liste oluşturur ve saklar. Bu fonksiyon birden çok giriş üzerinde tekrar tekrar kullanılıyorsa ya da benzer fonksiyonlar orijinal veriler üzerinde başka işlemler yapıyorsa bellek baskısı hızla artabilir.
Bir jeneratör fonksiyonu kullanma
Bunun yerine bir jeneratör fonksiyonu kullanılabilir:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print(output)Altyapı:
<generator object find_letter_occurrences at 0x102935e00>Altyapı:
Fonksiyon return yerine yield anahtar sözcüğünü içerir. Bu jeneratör fonksiyonu çağrıldığında bir jeneratör nesnesi döndürür ve bu nesne output değişkenine atanır. Bu nesne bir yineleyicidir. Her kelimedeki harfin kaç kez geçtiğine dair veriyi içermez. Bunun yerine, jeneratör değerlere ihtiyaç duyulduğunda oluşturur ve verir. Şimdi bu jeneratör yineleyiciden ilk değeri alalım:
next() yerleşik fonksiyonu, bir yineleyiciden bir sonraki değeri almanın yollarından biridir. Bu eğitimin ilerleyen kısımlarında diğer yöntemlere de bakacağız.
Jeneratör fonksiyonundaki kod, program yield anahtar sözcüğünün bulunduğu satıra ulaşana kadar yürütülür. Bu örnekte, for döngüsü ilk yinelemesine başlar ve words listesindeki ilk öğeyi alır. .count() string yöntemi bir tamsayı döndürür; bu durumda apple’da bir adet a olduğundan 1’dir. Jeneratör bu değeri verir ve next(output) tarafından döndürülür.
Jeneratör output bu noktada yürütmeyi duraklatır. Dolayısıyla jeneratör for döngüsünün ilk yinelemesini tamamlamış ve kelime listesindeki ilk kelimedeki a harfinin sayısını bulmuştur. Şimdi yeniden ihtiyaç duyulana kadar beklemektedir.
Yerleşik next() fonksiyonu output argümanıyla tekrar çağrılırsa, jeneratör duraklatıldığı yerden yürütmeye devam eder:
Jeneratör, for döngüsünün ilk yinelemesindeki yield satırından devam eder. for döngüsünde başka satır olmadığından döngünün başına döner ve words listesinden ikinci öğeyi alır. Bu sefer .count() tarafından döndürülen değer 3’tür ve bu değer verilir. Jeneratör yürütmeyi bu noktada yeniden duraklatır.
İkinci yineleme for döngüsünün sonuna ulaşır ve üçüncü yinelemeye geçilir. Kod tekrar yield satırına ilerler ve bu kez cherry’de a geçmediği için 0 tamsayısını verir.
Jeneratör yine duraklar. Program bu jeneratörün akıbetini, next() fonksiyonunu dördüncü kez çağırdığımızda belirler:
Yürütme, for döngüsünün üçüncü yinelemesinin sonundan devam eder. Ancak listede (words) artık öğe kalmadığı için döngü tamamlanmıştır. Jeneratör bir StopIteration hatası yükseltir.
Çoğu kullanımda jeneratör öğelerine doğrudan next() ile değil, başka bir yineleme süreciyle erişilir. StopIteration istisnası, yineleme sürecinin sona erdiğini bildirir. Bunu bu eğitimin bir sonraki bölümünde daha ayrıntılı inceleyeceğiz.
Önceki örnekte olduğu gibi, işlemi tek bir ifadeyle temsil edilebildiğinde Python’un jeneratör yineleyicileri oluşturmak için başka bir yolu daha vardır. output jeneratör yineleyicisi bir jeneratör ifadesi kullanılarak da oluşturulabilir:
words =["apple","banana","cherry"]
letter ="a"
output =(word.count(letter)for word in words)print(next(output))print(next(output))print(next(output))print(next(output))Altyapı:
output değişkenine atanan parantez içindeki ifade bir jeneratör ifadesidir ve find_letter_occurrences() jeneratör fonksiyonunun ürettiğine benzer bir jeneratör yineleyici oluşturur.
Bu bölümü, her bir öğeye ihtiyaç duyulduğunda yürütmenin nasıl duraklayıp devam ettiğini vurgulamak için bir başka jeneratör fonksiyonu örneğiyle bitirelim:
defshow_status():print("Start")yieldprint("Middle")yieldprint("End")yield
status = show_status()next(status)Altyapı:
Bu jeneratör fonksiyonunda döngü yoktur. Bunun yerine yield anahtar sözcüğünü içeren üç satır vardır. Kod, show_status() jeneratör fonksiyonu çağrıldığında bir jeneratör yineleyici status oluşturur. Program next(status)i ilk kez çağırdığında jeneratör çalışmaya başlar. "Start" dizesini yazdırır ve ilk yield ifadesinden sonra duraklar. yield anahtar sözcüğünü bir nesne izlemediği için jeneratör None verir.
Program "Middle" dizesini yalnızca next() ikinci kez çağrıldığında yazdırır:
Jeneratörleri kullanmanın daha fazla yolunu bir sonraki bölümde keşfedeceğiz.
Jeneratör Yineleyicilerle Çalışma
Jeneratör fonksiyonları jeneratör yineleyiciler oluşturur ve yineleyiciler yinelenebilirdir (iterable). Bu ifadeyi açalım. Program her jeneratör fonksiyonu çağırdığında bir yineleyici oluşturur. Yineleyiciler yinelenebilir olduğundan, for döngülerinde ve diğer yinelemeli süreçlerde kullanılabilirler.
Dolayısıyla next() yerleşik fonksiyonu bir yineleyicideki öğelere erişmenin tek yolu değildir. Bu bölüm jeneratörlerle çalışmanın diğer yollarını inceler.
Bu eğitimdeki önceki bir bölümde yer alan bir jeneratör fonksiyonuna geri dönelim:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)for value in output:print(value)Altyapı:
next()i birkaç kez kullanmak yerine, bu sürümde output jeneratör yineleyicisi bir for döngüsünde kullanılıyor. Yineleyiciler yinelenebilir olduğundan for döngülerinde kullanılabilirler. Döngü, jeneratör yineleyiciden değer kalmayana kadar öğeleri çeker.
Listeler ve demetler gibi veri yapılarından farklı olarak bir yineleyici yalnızca bir kez kullanılabilir. Aynı for döngüsünü ikinci kez çalıştırmaya çalışırsak kod değerleri yeniden yazdırmaz:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print("First attempt:")for value in output:print(value)print("Second attempt:")for value in output:print(value)Altyapı:
Yineleyici ilk for döngüsü tarafından tüketildiği için artık değer veremez. Jeneratöre tükendikten sonra yeniden ihtiyaç duyulursa, jeneratör fonksiyonundan yeni bir jeneratör yineleyici oluşturmamız gerekir.
Bir programda aynı anda birden fazla jeneratör yineleyicinin bulunması da mümkündür:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
first_output = find_letter_occurrences(words, letter)
second_output = find_letter_occurrences(words, letter)print("First value of first_output:")print(next(first_output))print("Values of second_output:")for value in second_output:print(value)print("Remaining values of first_output:")for value in first_output:print(value)Altyapı:
First value of first_output:
1
Values of second_output:
1
3
0
Remaining values of first_output:
3
0Altyapı:
find_letter_occurrences() jeneratör fonksiyonu iki jeneratör yineleyici oluşturur: first_output ve second_output. Her iki yineleyici de words listesindeki aynı verilere atıf yapsa da birbirlerinden bağımsız ilerlerler.
Bu örnek, first_outputtan ilk değeri next() ile alır. Jeneratör yineleyici 1’i verir ve bu noktada duraklar. Program daha sonra second_output üzerinde döngüye girer. Bu jeneratör henüz hiç değer vermediği için, döngü ikinci yineleyici tarafından verilen tüm değerleri dolaşır. Son olarak, first_output üzerinde yineleme yapan bir başka for döngüsü vardır. Ancak bu yineleyici programın başlarında ilk değerini zaten vermiştir. Döngü bu yineleyicideki kalan değerleri dolaşır.
for döngüsü, jeneratör yineleyiciler üzerinde yineleme yapmak için kullanılabilecek tek süreç değildir:
Bu örneklerde, jeneratör yineleyiciyi bir değişkene atamak yerine, program jeneratör fonksiyonunu doğrudan çağırarak oluşturur ve kullanır. İlk örnekte yineleyici yıldız gösterimiyle açılır (unpack edilir). Bu süreç, for döngüsüyle aynı yineleme protokolüne dayanır.
İkinci örnekte jeneratör yineleyici, yinelenebilir bir argüman gerektiren yerleşik sorted()a geçirilir. Jeneratörler yinelenebilirdir; bu nedenle Python’da yineleme gerçekleştiği her yerde kullanılabilirler.
Sonsuz yineleyiciler oluşturma
Bir jeneratör bir değer verir ve bir sonraki değere ihtiyaç duyulana kadar duraklar. Kod bir yineleyiciden her değer istediğinde, jeneratör fonksiyonu içindeki kod bir sonraki yield ifadesi değerlendirilene kadar çalışır. Bu eğitimdeki tüm örneklerde, jeneratör fonksiyonunda sonlu sayıda yield ifadesi vardı. Ancak jeneratör fonksiyonu içinde bir while döngüsü kullanarak sonsuz sayıda değer veren bir jeneratör oluşturmak mümkündür. Aşağıdaki örnekte jeneratör, kendisine geçirilen renkler listesinden rastgele bir renk verir:
import random
defget_color(colors):whileTrue:yield random.choice(colors)
output_colors = get_color(["red","green","blue"])print("First two colors:")print(next(output_colors))print(next(output_colors))print("Next 10 colors using a 'for' loop:")for _ inrange(10):print(next(output_colors))Altyapı:
First two colors:
green
red
Next 10 colors using a 'for' loop:
blue
green
green
green
red
red
red
blue
green
redAltyapı:
get_color() jeneratör fonksiyonu bir while döngüsü içinde bir yield ifadesine sahiptir. Dolayısıyla kod, bir sonraki değeri ararken her zaman yeni bir yield ifadesiyle karşılaşacaktır. output_colors jeneratör yineleyicisi, giriş listesinden rastgele seçilen sonsuz sayıda renk verir. Bu jeneratör asla tükenmez.
Listeler ve demetler gibi sonsuz veri yapıları oluşturmak mümkün değildir. Jeneratörler, bir programın sonsuz yinelenebilirler oluşturmasını sağlar. Jeneratör yineleyici doğrudan bir for döngüsünde kullanılırsa, döngünün sonsuza kadar çalışacağını unutmayın.
İleri Jeneratör Kavramları
Jeneratörlerin Python’da daha gelişmiş kullanım alanları vardır. Bu bölüm bunlardan bazılarını inceleyecek.
Jeneratöre bir nesne gönderme
Jeneratörler ayrıca kod değerlendirilirken kullanılabilecek ek verileri de kabul edebilir. yield anahtar sözcüğünü içeren ifade, bir değere değerlendiren bir ifadedir. Bu değer jeneratör fonksiyonu içinde bir değişkene atanabilir. Bu kavramı göstermek için basit bir örnekle başlayalım:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(next(output))print(next(output))Altyapı:
1
The yield expression evaluates to: None
2
The yield expression evaluates to: None
Traceback (most recent call last):
...
StopIterationAltyapı:
Python’daki yield anahtar sözcüğü, bir değere değerlendiren bir ifade oluşturur. Ancak jeneratör fonksiyonu içindeki bu ifadenin değeri, jeneratör tarafından verilen nesneyle aynı değildir. İlk yield ifadesini ele alalım. Jeneratör 1 tamsayısını verir. Dolayısıyla print(next(output)) ilk çağrıldığında 1’i gösterir ve jeneratörün yürütmesi duraklar.
Bununla birlikte, jeneratör içindeki yield ifadesi bir nesneye değerlendirilir ve bu nesne value adlı değişken adına atanır. Bu örnekte yield, value değişkenine None atar. Bu süreç jeneratör fonksiyonundaki yieldün ikinci geçişi için de tekrarlanır. Üçüncü next() çağrısının amacı, jeneratör fonksiyonundaki tüm kodun yürütüldüğünden emin olmaktır.
İkinci ve üçüncü next() çağrılarını, jeneratör sınıfındaki bir yöntem olan .send() ile değiştirelim:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(output.send("Here's a value"))print(output.send("Here's another value"))Altyapı:
1
The yield expression evaluates to: Here's a value
2
The yield expression evaluates to: Here's another value
Traceback (most recent call last):
...
StopIterationAltyapı:
Jeneratör fonksiyonu değişmedi. Jeneratör next() çağrılarak başlatılır ve kod ilk olarak 1 tamsayısını verene kadar çalışır. İkinci kez next() kullanmak yerine program output.send() çağrısını yapar. Bu yöntem, jeneratöre bir nesne gönderir. Bu örnekte nesne bir stringdir. Jeneratör fonksiyonundaki yield ifadesi bu stringe değerlendirilir ve value değişkenine atanır. Böylece jeneratör bu stringi kendi kodunda kullanabilir.
İkinci .send() çağrısı jeneratöre yeni bir nesne gönderir ve bu nesne aynı value değişkenine atanır. Son print() çağrısından sonra başka yield ifadesi kalmadığı için jeneratör bir StopIteration yükseltir.
.send() kullanımına başka bir örneğe bakalım. Aşağıdaki jeneratör bir hesaptaki bakiyeyi gösterir, ancak bakiye güncellenebilir:
Jeneratör fonksiyonu çağrıldığında bir başlangıç bakiyesi gerektirir. balanceın değeri, jeneratör kodu yürüttükçe değişebilir. .send() kullanılarak jeneratöre gönderilen her nesne amount değişkenine atanır. Bu değişken, eğer jeneratör bir değer verirken kendisine bir nesne gönderilmemişse None olur; gönderilmişse .send() ile yollanan nesneyi içerir.
current_balance jeneratör yineleyicisi 100 $ bakiyeyle başlar. Jeneratör, ilk değeri verene kadar kodu başlatan next() çağrısıyla başlatılır.
Jeneratör bir kez başlatıldıktan sonra, yürütmeyi next() yerine .send() ile de sürdürebilirsiniz. Jeneratör, gönderilen değeri bakiyeye ekler. Herhangi bir değer gönderilmezse, örneğin tekrar next() çağrılırsa, jeneratör değişmemiş bakiyeyi verir.
Başka bir yinelenebilirden doğrudan değer vermek
Python jeneratörleri, yield from sözdizimini kullanarak başka bir jeneratörden veya yinelenebilirden doğrudan değerler de verebilir. İç içe geçmiş bir listeden değerler veren bir jeneratör fonksiyonu örneğine bakalım:
defflatten(nested_list):for item in nested_list:ifisinstance(item,list):yieldfrom flatten(item)else:yield item
nested_list =[1,[2,3],[4,[5,6]],7]print(list(flatten(nested_list)))Altyapı:
Jeneratör fonksiyonu, içinde iç içe listeler barındırabilen bir liste alır. for döngüsü listedeki öğeleri yineler. Dış listedeki her öğe ya bir değer (bu örnekte tamsayı) ya da başka bir listedir. Öğenin liste olmadığı durumda jeneratör öğeyi verir.
Ancak öğe bir listese, jeneratör, iç listeyi argüman olarak vererek flatten() jeneratör fonksiyonunu özyineli (recursive) olarak yeniden çağırır. Bu, veri kaynağı olarak iç listeyi kullanan başka bir jeneratör yineleyici oluşturur. Bu satır bir yield ifadesi kullansaydı, ilk jeneratör ikinci jeneratörü verirdi. Bunun yerine yield from kullanarak, ilk jeneratör ikinci jeneratörden gelen değerleri verir.
Özet: yield ve return
return ve yield ile tanımlanan fonksiyonlar benzer görünse de davranışları farklıdır. Temel farkları özetleyelim:
Normal Fonksiyon
Jeneratör Fonksiyonu
Anahtar Sözcük
return (açıkça kullanılmasa da örtük olarak)
yield
Çağrıldığında
return’e ulaşana kadar kodu yürütür, ardından nihai değeri döndürür
Bir jeneratör yineleyici oluşturur
Sonlanma
return ifadesiyle sonlanır
yield ile duraklar, daha sonra devam edebilir
Döndürülen Değer
Tek bir nesne (bir veri yapısı olabilir)
Jeneratör yineleyici
Yield İfadesi
Uygulanamaz (bir ifade oluşturur)
None’a veya .send() ile gönderilen değere değerlendirir
Kullanım Senaryoları
Nihai bir sonuç döndürmek için idealdir
Özellikle büyük veya sonsuz diziler için bir veri akışı oluşturmakta idealdir
Sonuç
Python’un yield anahtar sözcüğü, bir jeneratör fonksiyonu tanımlamak için fonksiyonlarda kullanılır. Bu fonksiyonlar çağrıldığında jeneratör yineleyiciler oluşturur. Jeneratörler, ifadelerin, ifade yürütüldüğünde değil değere ihtiyaç duyulduğunda değerlendirildiği, Python’daki tembel değerlendirmenin bir örneğidir. Bu nedenle yield ifadesi, değerlerin bellekte saklanmasına gerek kalmadan talep üzerine üretildiği bir veri akışı oluşturmak için kullanışlıdır.
Büyük veri kümeleriyle çalışırken ve çok sayıda işlem gerektiğinde verimlilik önemli bir husustur. Python’un jeneratör yineleyicileri, büyük miktarda veriyi verimli bir şekilde işlemek için gereken temel araçlardan biridir.
Malta Üniversitesi’nde lisans düzeyinde Fizik ve Matematik okudum. Ardından Londra’ya taşındım ve Imperial College’da Fizik alanında doktoramı tamamladım. İnsan retinasını görüntülemek için yeni optik teknikler üzerine çalıştım. Şimdi Python hakkında yazmaya, Python’u anlatmaya ve Python öğretmeye odaklanıyorum.
Algoritmalar, sistem tasarımı ve davranışsal senaryoları kapsayan bu temel sorularla teknik mülakat sürecine hakim olun. Uzman cevapları, kod örnekleri ve kanıtlanmış hazırlık stratejileri edinin.
Git deponuzu temiz tutmak için .gitignore’u nasıl kullanacağınızı öğrenin. Bu eğitim; temelleri, yaygın kullanım durumlarını ve başlamanıza yardımcı olacak pratik örnekleri kapsar!
Fonksiyon yazmaktan sınıflar tanımlamaya kadar yazılım geliştirme için Python öğrenin. Geliştirici olarak kariyerinize başlamanız için gereken becerileri kazanın!
Python fonksiyonlarının her zaman bir
returnifadesi olmak zorunda değildir. Jeneratör fonksiyonları,returnyerineyieldanahtar sözcüğünü içeren fonksiyonlardır.Bu fonksiyonlar bir veri akışını temsil eden jeneratör yineleyiciler üretir. Bir yineleyici tarafından temsil edilen öğeler yalnızca gerektiğinde oluşturulur ve verilir. Bu tür değerlendirme genellikle tembel değerlendirme (lazy evaluation) olarak anılır.
Büyük veri kümeleriyle çalışırken, jeneratörler, öğelerinin her biri için bellekte yer gerektiren listeler, demetler ve diğer veri yapılarında veriyi saklamaya kıyasla bellek açısından verimli bir alternatif sunar. Jeneratör fonksiyonları ayrıca, listeler ve demetler gibi hevesli (eager) değerlendirilen yapılarla mümkün olmayan sonsuz yineleyiciler de oluşturabilir.
Başlamadan önce, fonksiyonlar ile jeneratörler arasındaki farkları kısaca hatırlayalım:
Özellik
Fonksiyon
Jeneratör
Değer Üretimi
Tüm değerleri bir kerede döndürür
Değerleri talep üzerine, teker teker verir
Yürütme
Döndürmeden önce tamamen çalışır
Değer verdikten sonra duraklar, bir sonraki değer istendiğinde devam eder
Anahtar Sözcük
return
yield
Bellek Kullanımı
Potansiyel olarak yüksek; tüm diziyi bellekte tutar
Düşük; yalnızca geçerli değeri ve bir sonraki için durumu tutar
Yineleme
Birden çok yineleme mümkün, ancak tüm dizinin saklanmasını gerektirir
Tek geçişli yineleme için tasarlanmıştır; büyük veya sonsuz dizilerde daha verimlidir
Python'un
yieldAnahtar Sözcüğünü Kullanarak Jeneratör Fonksiyonları OluşturmaPython’da jeneratör terimi, bir jeneratör yineleyiciyi veya bir jeneratör fonksiyonunu ifade edebilir. Bunlar Python’da farklı fakat ilişkili nesnelerdir. Bu eğitimde karışıklığı önlemek için tam terimler sıkça kullanılmaktadır.
Önce jeneratör fonksiyonlarını inceleyelim. Bir jeneratör fonksiyonu, normal bir fonksiyona benzer görünür ancak
returnyerineyieldanahtar sözcüğünü içerir.Bir Python programı bir jeneratör fonksiyonunu çağırdığında, bir jeneratör yineleyici oluşturur. Yineleyiciler talep üzerine bir değer verir ve başka bir değer gerektiğinde yürütmeyi duraklatır. Bu kavramı açıklamak ve normal fonksiyonlarla jeneratör fonksiyonları arasındaki farkı göstermek için bir örneğe bakalım.
Normal bir fonksiyon kullanma
Önce, bir
returnifadesi içeren normal bir fonksiyon tanımlayalım. Bu fonksiyon bir kelime dizisi ve bir harf alır ve her kelimedeki söz konusu harfin kaç kez geçtiğini içeren bir liste döndürür:Fonksiyon, listedeki 1, 3 ve 0 değerlerini çıktılar çünkü apple’da bir, banana’da üç ve cherry’de hiç a yok. Aynı fonksiyon, boş bir liste başlatıp
.append()kullanmak yerine liste üreteçleriyle (list comprehension) yeniden yazılabilir:Bu normal fonksiyon her çağrıldığında tüm sonuçları içeren bir liste döndürür. Ancak kelime listesi büyükse, bu normal fonksiyonu çağırmak bellek üzerinde baskı oluşturur; çünkü program, orijinaliyle aynı boyutta yeni bir liste oluşturur ve saklar. Bu fonksiyon birden çok giriş üzerinde tekrar tekrar kullanılıyorsa ya da benzer fonksiyonlar orijinal veriler üzerinde başka işlemler yapıyorsa bellek baskısı hızla artabilir.
Bir jeneratör fonksiyonu kullanma
Bunun yerine bir jeneratör fonksiyonu kullanılabilir:
Fonksiyon
returnyerineyieldanahtar sözcüğünü içerir. Bu jeneratör fonksiyonu çağrıldığında bir jeneratör nesnesi döndürür ve bu nesneoutputdeğişkenine atanır. Bu nesne bir yineleyicidir. Her kelimedeki harfin kaç kez geçtiğine dair veriyi içermez. Bunun yerine, jeneratör değerlere ihtiyaç duyulduğunda oluşturur ve verir. Şimdi bu jeneratör yineleyiciden ilk değeri alalım:next()yerleşik fonksiyonu, bir yineleyiciden bir sonraki değeri almanın yollarından biridir. Bu eğitimin ilerleyen kısımlarında diğer yöntemlere de bakacağız.Jeneratör fonksiyonundaki kod, program
yieldanahtar sözcüğünün bulunduğu satıra ulaşana kadar yürütülür. Bu örnekte,fordöngüsü ilk yinelemesine başlar vewordslistesindeki ilk öğeyi alır..count()string yöntemi bir tamsayı döndürür; bu durumda apple’da bir adet a olduğundan 1’dir. Jeneratör bu değeri verir venext(output)tarafından döndürülür.Jeneratör
outputbu noktada yürütmeyi duraklatır. Dolayısıyla jeneratörfordöngüsünün ilk yinelemesini tamamlamış ve kelime listesindeki ilk kelimedeki a harfinin sayısını bulmuştur. Şimdi yeniden ihtiyaç duyulana kadar beklemektedir.Yerleşik
next()fonksiyonuoutputargümanıyla tekrar çağrılırsa, jeneratör duraklatıldığı yerden yürütmeye devam eder:Jeneratör,
fordöngüsünün ilk yinelemesindekiyieldsatırından devam eder.fordöngüsünde başka satır olmadığından döngünün başına döner vewordslistesinden ikinci öğeyi alır. Bu sefer.count()tarafından döndürülen değer 3’tür ve bu değer verilir. Jeneratör yürütmeyi bu noktada yeniden duraklatır.Üçüncü
next()çağrısı bu yürütmeyi sürdürür:İkinci yineleme
fordöngüsünün sonuna ulaşır ve üçüncü yinelemeye geçilir. Kod tekraryieldsatırına ilerler ve bu kez cherry’de a geçmediği için 0 tamsayısını verir.Jeneratör yine duraklar. Program bu jeneratörün akıbetini,
next()fonksiyonunu dördüncü kez çağırdığımızda belirler:Yürütme,
fordöngüsünün üçüncü yinelemesinin sonundan devam eder. Ancak listede (words) artık öğe kalmadığı için döngü tamamlanmıştır. Jeneratör birStopIterationhatası yükseltir.Çoğu kullanımda jeneratör öğelerine doğrudan
next()ile değil, başka bir yineleme süreciyle erişilir.StopIterationistisnası, yineleme sürecinin sona erdiğini bildirir. Bunu bu eğitimin bir sonraki bölümünde daha ayrıntılı inceleyeceğiz.Önceki örnekte olduğu gibi, işlemi tek bir ifadeyle temsil edilebildiğinde Python’un jeneratör yineleyicileri oluşturmak için başka bir yolu daha vardır.
outputjeneratör yineleyicisi bir jeneratör ifadesi kullanılarak da oluşturulabilir:outputdeğişkenine atanan parantez içindeki ifade bir jeneratör ifadesidir vefind_letter_occurrences()jeneratör fonksiyonunun ürettiğine benzer bir jeneratör yineleyici oluşturur.Bu bölümü, her bir öğeye ihtiyaç duyulduğunda yürütmenin nasıl duraklayıp devam ettiğini vurgulamak için bir başka jeneratör fonksiyonu örneğiyle bitirelim:
Bu jeneratör fonksiyonunda döngü yoktur. Bunun yerine
yieldanahtar sözcüğünü içeren üç satır vardır. Kod,show_status()jeneratör fonksiyonu çağrıldığında bir jeneratör yineleyicistatusoluşturur. Programnext(status)i ilk kez çağırdığında jeneratör çalışmaya başlar."Start"dizesini yazdırır ve ilkyieldifadesinden sonra duraklar.yieldanahtar sözcüğünü bir nesne izlemediği için jeneratörNoneverir.Program
"Middle"dizesini yalnızcanext()ikinci kez çağrıldığında yazdırır:Jeneratör ikinci
yieldifadesinden sonra duraklar.next()üçüncü kez çağrıldığında son dize olan"End"yazdırılır:Jeneratör son
yieldifadesinde duraklar. Program bu jeneratör yineleyiciden bir değer istediğinde birStopIterationhatası yükseltir:Jeneratörleri kullanmanın daha fazla yolunu bir sonraki bölümde keşfedeceğiz.
Jeneratör Yineleyicilerle Çalışma
Jeneratör fonksiyonları jeneratör yineleyiciler oluşturur ve yineleyiciler yinelenebilirdir (iterable). Bu ifadeyi açalım. Program her jeneratör fonksiyonu çağırdığında bir yineleyici oluşturur. Yineleyiciler yinelenebilir olduğundan,
fordöngülerinde ve diğer yinelemeli süreçlerde kullanılabilirler.Dolayısıyla
next()yerleşik fonksiyonu bir yineleyicideki öğelere erişmenin tek yolu değildir. Bu bölüm jeneratörlerle çalışmanın diğer yollarını inceler.Jeneratör yineleyicilerle Python’un yineleme protokolünü kullanma
Bu eğitimdeki önceki bir bölümde yer alan bir jeneratör fonksiyonuna geri dönelim:
next()i birkaç kez kullanmak yerine, bu sürümdeoutputjeneratör yineleyicisi birfordöngüsünde kullanılıyor. Yineleyiciler yinelenebilir olduğundanfordöngülerinde kullanılabilirler. Döngü, jeneratör yineleyiciden değer kalmayana kadar öğeleri çeker.Listeler ve demetler gibi veri yapılarından farklı olarak bir yineleyici yalnızca bir kez kullanılabilir. Aynı
fordöngüsünü ikinci kez çalıştırmaya çalışırsak kod değerleri yeniden yazdırmaz:Yineleyici ilk
fordöngüsü tarafından tüketildiği için artık değer veremez. Jeneratöre tükendikten sonra yeniden ihtiyaç duyulursa, jeneratör fonksiyonundan yeni bir jeneratör yineleyici oluşturmamız gerekir.Bir programda aynı anda birden fazla jeneratör yineleyicinin bulunması da mümkündür:
find_letter_occurrences()jeneratör fonksiyonu iki jeneratör yineleyici oluşturur:first_outputvesecond_output. Her iki yineleyici dewordslistesindeki aynı verilere atıf yapsa da birbirlerinden bağımsız ilerlerler.Bu örnek,
first_outputtan ilk değerinext()ile alır. Jeneratör yineleyici 1’i verir ve bu noktada duraklar. Program daha sonrasecond_outputüzerinde döngüye girer. Bu jeneratör henüz hiç değer vermediği için, döngü ikinci yineleyici tarafından verilen tüm değerleri dolaşır. Son olarak,first_outputüzerinde yineleme yapan bir başkafordöngüsü vardır. Ancak bu yineleyici programın başlarında ilk değerini zaten vermiştir. Döngü bu yineleyicideki kalan değerleri dolaşır.fordöngüsü, jeneratör yineleyiciler üzerinde yineleme yapmak için kullanılabilecek tek süreç değildir:Bu örneklerde, jeneratör yineleyiciyi bir değişkene atamak yerine, program jeneratör fonksiyonunu doğrudan çağırarak oluşturur ve kullanır. İlk örnekte yineleyici yıldız gösterimiyle açılır (unpack edilir). Bu süreç,
fordöngüsüyle aynı yineleme protokolüne dayanır.İkinci örnekte jeneratör yineleyici, yinelenebilir bir argüman gerektiren yerleşik
sorted()a geçirilir. Jeneratörler yinelenebilirdir; bu nedenle Python’da yineleme gerçekleştiği her yerde kullanılabilirler.Sonsuz yineleyiciler oluşturma
Bir jeneratör bir değer verir ve bir sonraki değere ihtiyaç duyulana kadar duraklar. Kod bir yineleyiciden her değer istediğinde, jeneratör fonksiyonu içindeki kod bir sonraki
yieldifadesi değerlendirilene kadar çalışır. Bu eğitimdeki tüm örneklerde, jeneratör fonksiyonunda sonlu sayıdayieldifadesi vardı. Ancak jeneratör fonksiyonu içinde birwhiledöngüsü kullanarak sonsuz sayıda değer veren bir jeneratör oluşturmak mümkündür. Aşağıdaki örnekte jeneratör, kendisine geçirilen renkler listesinden rastgele bir renk verir:get_color()jeneratör fonksiyonu birwhiledöngüsü içinde biryieldifadesine sahiptir. Dolayısıyla kod, bir sonraki değeri ararken her zaman yeni biryieldifadesiyle karşılaşacaktır.output_colorsjeneratör yineleyicisi, giriş listesinden rastgele seçilen sonsuz sayıda renk verir. Bu jeneratör asla tükenmez.Listeler ve demetler gibi sonsuz veri yapıları oluşturmak mümkün değildir. Jeneratörler, bir programın sonsuz yinelenebilirler oluşturmasını sağlar. Jeneratör yineleyici doğrudan bir
fordöngüsünde kullanılırsa, döngünün sonsuza kadar çalışacağını unutmayın.İleri Jeneratör Kavramları
Jeneratörlerin Python’da daha gelişmiş kullanım alanları vardır. Bu bölüm bunlardan bazılarını inceleyecek.
Jeneratöre bir nesne gönderme
Jeneratörler ayrıca kod değerlendirilirken kullanılabilecek ek verileri de kabul edebilir.
yieldanahtar sözcüğünü içeren ifade, bir değere değerlendiren bir ifadedir. Bu değer jeneratör fonksiyonu içinde bir değişkene atanabilir. Bu kavramı göstermek için basit bir örnekle başlayalım:Python’daki
yieldanahtar sözcüğü, bir değere değerlendiren bir ifade oluşturur. Ancak jeneratör fonksiyonu içindeki bu ifadenin değeri, jeneratör tarafından verilen nesneyle aynı değildir. İlkyieldifadesini ele alalım. Jeneratör 1 tamsayısını verir. Dolayısıylaprint(next(output))ilk çağrıldığında 1’i gösterir ve jeneratörün yürütmesi duraklar.Bununla birlikte, jeneratör içindeki
yieldifadesi bir nesneye değerlendirilir ve bu nesnevalueadlı değişken adına atanır. Bu örnekteyield,valuedeğişkenineNoneatar. Bu süreç jeneratör fonksiyonundakiyieldün ikinci geçişi için de tekrarlanır. Üçüncünext()çağrısının amacı, jeneratör fonksiyonundaki tüm kodun yürütüldüğünden emin olmaktır.İkinci ve üçüncü
next()çağrılarını, jeneratör sınıfındaki bir yöntem olan.send()ile değiştirelim:Jeneratör fonksiyonu değişmedi. Jeneratör
next()çağrılarak başlatılır ve kod ilk olarak 1 tamsayısını verene kadar çalışır. İkinci keznext()kullanmak yerine programoutput.send()çağrısını yapar. Bu yöntem, jeneratöre bir nesne gönderir. Bu örnekte nesne bir stringdir. Jeneratör fonksiyonundakiyieldifadesi bu stringe değerlendirilir vevaluedeğişkenine atanır. Böylece jeneratör bu stringi kendi kodunda kullanabilir.İkinci
.send()çağrısı jeneratöre yeni bir nesne gönderir ve bu nesne aynıvaluedeğişkenine atanır. Sonprint()çağrısından sonra başkayieldifadesi kalmadığı için jeneratör birStopIterationyükseltir..send()kullanımına başka bir örneğe bakalım. Aşağıdaki jeneratör bir hesaptaki bakiyeyi gösterir, ancak bakiye güncellenebilir:Jeneratör fonksiyonu çağrıldığında bir başlangıç bakiyesi gerektirir.
balanceın değeri, jeneratör kodu yürüttükçe değişebilir..send()kullanılarak jeneratöre gönderilen her nesneamountdeğişkenine atanır. Bu değişken, eğer jeneratör bir değer verirken kendisine bir nesne gönderilmemişseNoneolur; gönderilmişse.send()ile yollanan nesneyi içerir.current_balancejeneratör yineleyicisi 100 $ bakiyeyle başlar. Jeneratör, ilk değeri verene kadar kodu başlatannext()çağrısıyla başlatılır.Jeneratör bir kez başlatıldıktan sonra, yürütmeyi
next()yerine.send()ile de sürdürebilirsiniz. Jeneratör, gönderilen değeri bakiyeye ekler. Herhangi bir değer gönderilmezse, örneğin tekrarnext()çağrılırsa, jeneratör değişmemiş bakiyeyi verir.Başka bir yinelenebilirden doğrudan değer vermek
Python jeneratörleri,
yield fromsözdizimini kullanarak başka bir jeneratörden veya yinelenebilirden doğrudan değerler de verebilir. İç içe geçmiş bir listeden değerler veren bir jeneratör fonksiyonu örneğine bakalım:Jeneratör fonksiyonu, içinde iç içe listeler barındırabilen bir liste alır.
fordöngüsü listedeki öğeleri yineler. Dış listedeki her öğe ya bir değer (bu örnekte tamsayı) ya da başka bir listedir. Öğenin liste olmadığı durumda jeneratör öğeyi verir.Ancak öğe bir listese, jeneratör, iç listeyi argüman olarak vererek
flatten()jeneratör fonksiyonunu özyineli (recursive) olarak yeniden çağırır. Bu, veri kaynağı olarak iç listeyi kullanan başka bir jeneratör yineleyici oluşturur. Bu satır biryieldifadesi kullansaydı, ilk jeneratör ikinci jeneratörü verirdi. Bunun yerineyield fromkullanarak, ilk jeneratör ikinci jeneratörden gelen değerleri verir.Özet:
yieldvereturnreturnveyieldile tanımlanan fonksiyonlar benzer görünse de davranışları farklıdır. Temel farkları özetleyelim:Normal Fonksiyon
Jeneratör Fonksiyonu
Anahtar Sözcük
return (açıkça kullanılmasa da örtük olarak)
yield
Çağrıldığında
return’e ulaşana kadar kodu yürütür, ardından nihai değeri döndürür
Bir jeneratör yineleyici oluşturur
Sonlanma
return ifadesiyle sonlanır
yield ile duraklar, daha sonra devam edebilir
Döndürülen Değer
Tek bir nesne (bir veri yapısı olabilir)
Jeneratör yineleyici
Yield İfadesi
Uygulanamaz (bir ifade oluşturur)
None’a veya .send() ile gönderilen değere değerlendirir
Kullanım Senaryoları
Nihai bir sonuç döndürmek için idealdir
Özellikle büyük veya sonsuz diziler için bir veri akışı oluşturmakta idealdir
Sonuç
Python’un
yieldanahtar sözcüğü, bir jeneratör fonksiyonu tanımlamak için fonksiyonlarda kullanılır. Bu fonksiyonlar çağrıldığında jeneratör yineleyiciler oluşturur. Jeneratörler, ifadelerin, ifade yürütüldüğünde değil değere ihtiyaç duyulduğunda değerlendirildiği, Python’daki tembel değerlendirmenin bir örneğidir. Bu nedenleyieldifadesi, değerlerin bellekte saklanmasına gerek kalmadan talep üzerine üretildiği bir veri akışı oluşturmak için kullanışlıdır.Büyük veri kümeleriyle çalışırken ve çok sayıda işlem gerektiğinde verimlilik önemli bir husustur. Python’un jeneratör yineleyicileri, büyük miktarda veriyi verimli bir şekilde işlemek için gereken temel araçlardan biridir.
Python hakkında daha fazla bilgi edinmek istiyorsanız, şu Python Developer kariyer yoluna göz atın.