Python Generator Kavramı
Kullanım Senaryosu:
- Bir fonksiyon çok yüksek miktarda veri dönüyorsa ve memory gereksiz yere şişirilmek istenmiyorsa (aşırı yükten sisteminiz crash olabilir, diğer process'leriniz olumsuz etkilenebilir.) generator fonksiyon oldukça faydalı olacaktır.
Generator fonksiyonlar bir iterator gibi davranan fonksiyonlar tanımlamamıza olanak sağlar.
Python'ın iterator protokolünü implement eder.
PEP 255 'te tanıtılmıştır.
Bir list için nasıl loop ile içerisindeki veriye erişebiliyorsak, generator'ların ürettiği veriye de loop ile erişebiliriz.
Bir generator oluşturabilmek için
yieldkeyword'unu ya da comprehension'ları kullanıyoruz.yieldkeyword'u sadece fonksiyonların içerisinde kullanılabilir.yieldkeyword'u normal bir fonksiyonu generator fonksiyonuna dönüştürür.Generator tanımlamak için ve muhafaza ettiği veriye erişebilmek için aşağıdaki örnekleri inceleyelim
xxxxxxxxxx# generator `fonksiyon` tanımlamak için yield anahtar kelimesini kullanıyoruz.In [1]: def _gen():...: yield 30...: yield 44...:# comprehension yapısını kullanarak generator `objesi` oluşturabiliriz.In [2]: _comp_gen = (i for i in [30, 44])In [3]: _gen # fonksiyonu call veya inkove etmeden generator `objesi` elde edemeyiz.Out[3]: <function __main__._gen()>In [4]: _gen() # fonksiyonu call ederek generator objesi elde ediyoruzOut[4]: <generator object _gen at 0x7fb7e1d8cf20>In [5]: _comp_gen # comprehension yapısı ile direkt generator objesi elde etmekteyiz.Out[5]: <generator object <genexpr> at 0x7fb7e1d8cd60>In [6]: g = _gen()In [7]: gOut[7]: <generator object _gen at 0x7fb7e1cd2120># next fonksiyonu generator'a bir sonraki yield statement'ına kadar çalışmasını söyler.In [8]: next(g) # yield 30 çalışır ve generator fonksiyon pause eder.Out[8]: 30In [9]: next(g) # sonraki yield statement'a kadar çalış!Out[9]: 44In [10]: next(g) # sonraki yield statement'a kadar çalış!---------------------------------------------------------------------------StopIteration Traceback (most recent call last)<ipython-input-10-e734f8aca5ac> in <module>----> 1 next(g)StopIteration:############################ Sona geldiğinde StopIteration raise eder ve generator'ın exhausted olduğunu gösterir.###########################In [11]: next(_comp_gen)Out[11]: 30In [12]: next(_comp_gen)Out[12]: 44In [13]: next(_comp_gen)---------------------------------------------------------------------------StopIteration Traceback (most recent call last)<ipython-input-13-e2cf0fe31721> in <module>----> 1 next(_comp_gen)StopIteration:forloop ile de generator'ların ürettği veriye erişebilmekteyizxxxxxxxxxxIn [14]: mygen = (i for i in range(10))In [15]: mygenOut[15]: <generator object <genexpr> at 0x7fb7e1cd20b0>In [16]: for data in mygen:...: print(data)...:0123456789
yield vs return
returnstatement fonksiyondaki değeri verdikten sonra fonksiyonu sonlandırır.yieldstatement'a geldiğimizde programımız çalışmayı duraklatır(pause veya suspend) veyielddeğerini çağırılan yere döndürür(verir).- Bir fonksiyon duraksamaya geçtiğinde(pause veya suspend), fonksiyonun state'i(değişkenler, instruction pointer, internal stack, exception vs. de dahil) kaydedilir.
xxxxxxxxxxIn [23]: from random import randint ...: ...: def get_random_ints(count, begin, end): ...: print("get_random_ints start") ...: list_numbers = [] ...: for x in range(0, count): ...: list_numbers.append(randint(begin, end)) ...: print("get_random_ints end") ...: return list_numbers ...: ...: ...: print(type(get_random_ints)) ...: nums = get_random_ints(10, 0, 100) ...: print(nums)<class 'function'>get_random_ints startget_random_ints end[92, 29, 78, 77, 79, 58, 26, 43, 54, 29]###############################################################################################In [24]: def get_random_ints(count, begin, end): ...: print("get_random_ints start") ...: for x in range(0, count): ...: yield randint(begin, end) ...: print("get_random_ints end") # generator exhausted olmadığı sürece bu satır çalışmayacak. ...: ...: ...: nums_generator = get_random_ints(10, 0, 100) ...: print(type(nums_generator)) ...: for i in nums_generator: ...: print(i) ...: <class 'generator'>get_random_ints start826709920965138768get_random_ints end
Gerçek Dünyadan Örnek
En yaygın senaryolardan birisi yüksek boyutlu dosya okuma işlemidir.
read_file.pyxxxxxxxxxximport resourceimport sysdef read_file(file_name):text_file = open(file_name, 'r')line_list = text_file.readlines()text_file.close()return line_listfile_lines = read_file(sys.argv[1])print(type(file_lines))print(f'line number: {len(file_lines)}')for line in file_lines:# print(line)passprint('Peak Memory Usage =', resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)print('User Mode Time =', resource.getrusage(resource.RUSAGE_SELF).ru_utime)print('System Mode Time =', resource.getrusage(resource.RUSAGE_SELF).ru_stime)
read_file_yield.pyxxxxxxxxxximport resourceimport sysdef read_file_yield(file_name):text_file = open(file_name, 'r')while True:line_data = text_file.readline()if not line_data:text_file.close()breakyield line_datafile_data = read_file_yield(sys.argv[1])print(type(file_data))for l in file_data:# print(l)passprint('Peak Memory Usage =', resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)print('User Mode Time =', resource.getrusage(resource.RUSAGE_SELF).ru_utime)print('System Mode Time =', resource.getrusage(resource.RUSAGE_SELF).ru_stime)
İki aded dosyayı çalıştırıp sonuçlarını görelim. Dosyaların boyutları aşağıdaki gibidir
xxxxxxxxxx└─ λ du -sh total.txt all.txt1,5M total.txt7,1M all.txt
x
λ python read_file.py total.txt <class 'list'>line number: 109774Peak Memory Usage = 17508User Mode Time = 0.043507System Mode Time = 0.0039559999999999994
λ python read_file.py all.txt <class 'list'>line number: 548870Peak Memory Usage = 48608User Mode Time = 0.058794System Mode Time = 0.02352################################################################λ python read_file_yield.py total.txt <class 'generator'>Peak Memory Usage = 9384User Mode Time = 0.033124System Mode Time = 0.0
λ python read_file_yield.py all.txt <class 'generator'>Peak Memory Usage = 9536User Mode Time = 0.092669System Mode Time = 0.023167999999999998#################################################################
| Dosya boyutu | return | yield |
|---|---|---|
| 1.5 MB | Memory: 17.508 MB, Time: 0.04s | Memory: 9.384 MB, Time: 0.03 s |
| 7.1 MB | Memory: 54.887 MB, Time: 0.08s | Memory: 9.536 MB, Time: 0.11s |
yieldstatementreturne göre biraz yavaş çünkü generator fonksiyonunun state'inin izlenmesi(track) gerekiyor.- Ancak memory kullanımı
yieldstatement'tareturnstatement'a göre oldukça performanslı.
Generator'lara veri göndermek
send fonksiyonu ile generator'lara veri gönderebiliriz. Aşağıdaki yapıyı inceleyelim
xxxxxxxxxxIn [25]: def proc(): ...: while True: ...: received = yield # buradaki statement dışarıdan aldığı veriyi received değişkenine assign eder. ...: print(f'received: {received}') ...:
In [26]: p = proc()# generator'u başlatmamız gerekiyor yoksa bu hatayı alırız: TypeError: can’t send non-None value to a just-started generator.In [27]: p.send(None) # starting the generator, we can use next(p) as well
In [28]: p.send(44)received: 44
In [29]: p.send('adnan')received: adnan
yield from expression
Generatorları chain etmek için ve iterable'ları birlikte kullanmaya olanak verir.
verilen expression dan sub-iterator oluşturmayı sağlar. sub-iterator tarafından üretilen bütün değerler çağrılan yere pass edilir.
xxxxxxxxxxIn [30]: def coro():...: while True:...: received = yield...: print(f'received: {received}')...:In [31]: def master_coro(coroutine):...: yield from coroutine...:In [32]: mc = master_coro(coro())In [33]: mc.send(None)In [34]: mc.send('adnan kaya')received: adnan kayaIn [35]: mc.send(30)received: 30x
In [36]: def coro1():...: while True:...: received = yield...: print(f'coro1 received: {received}')...:In [37]: def coro2():...: while True:...: received = yield...: print(f'coro2 received*2: {received*2 if received else 0}')...:In [38]: def master_coro():...: while True:...: selected = yield...: if selected == 1:...: yield from coro1()...: elif selected == 2:...: yield from coro2()...: else:...: print('unknown operator!')...: break...:In [39]: mc = master_coro()In [40]: mc.send(None) # start master coroIn [41]: mc.send(2)In [42]: mc.send(5)coro2 received*2: 10In [43]: mc.send(None)coro2 received*2: 0
Kaynaklar
- https://www.askpython.com/python/python-yield-examples
- https://realpython.com/introduction-to-python-generators/
- https://stackoverflow.com/questions/102535/what-can-you-use-generator-functions-for/23530101#23530101
Yorumlar