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
yield
keyword'unu ya da comprehension'ları kullanıyoruz.yield
keyword'u sadece fonksiyonların içerisinde kullanılabilir.yield
keyword'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 ediyoruz
Out[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]: g
Out[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]: 30
In [9]: next(g) # sonraki yield statement'a kadar çalış!
Out[9]: 44
In [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]: 30
In [12]: next(_comp_gen)
Out[12]: 44
In [13]: next(_comp_gen)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-13-e2cf0fe31721> in <module>
----> 1 next(_comp_gen)
StopIteration:
for
loop ile de generator'ların ürettği veriye erişebilmekteyizxxxxxxxxxx
In [14]: mygen = (i for i in range(10))
In [15]: mygen
Out[15]: <generator object <genexpr> at 0x7fb7e1cd20b0>
In [16]: for data in mygen:
...: print(data)
...:
0
1
2
3
4
5
6
7
8
9
yield
vs return
return
statement fonksiyondaki değeri verdikten sonra fonksiyonu sonlandırır.yield
statement'a geldiğimizde programımız çalışmayı duraklatır(pause veya suspend) veyield
değ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.
xxxxxxxxxx
In [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 start
get_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 start
8
26
70
99
20
96
51
3
87
68
get_random_ints end
Gerçek Dünyadan Örnek
En yaygın senaryolardan birisi yüksek boyutlu dosya okuma işlemidir.
read_file.py
xxxxxxxxxx
import resource
import sys
def read_file(file_name):
text_file = open(file_name, 'r')
line_list = text_file.readlines()
text_file.close()
return line_list
file_lines = read_file(sys.argv[1])
print(type(file_lines))
print(f'line number: {len(file_lines)}')
for line in file_lines:
# print(line)
pass
print('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.py
xxxxxxxxxx
import resource
import sys
def 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()
break
yield line_data
file_data = read_file_yield(sys.argv[1])
print(type(file_data))
for l in file_data:
# print(l)
pass
print('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.txt
1,5M total.txt
7,1M all.txt
x
λ python read_file.py total.txt
<class 'list'>
line number: 109774
Peak Memory Usage = 17508
User Mode Time = 0.043507
System Mode Time = 0.0039559999999999994
λ python read_file.py all.txt
<class 'list'>
line number: 548870
Peak Memory Usage = 48608
User Mode Time = 0.058794
System Mode Time = 0.02352
################################################################
λ python read_file_yield.py total.txt
<class 'generator'>
Peak Memory Usage = 9384
User Mode Time = 0.033124
System Mode Time = 0.0
λ python read_file_yield.py all.txt
<class 'generator'>
Peak Memory Usage = 9536
User Mode Time = 0.092669
System 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 |
yield
statementreturn
e göre biraz yavaş çünkü generator fonksiyonunun state'inin izlenmesi(track) gerekiyor.- Ancak memory kullanımı
yield
statement'tareturn
statement'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
xxxxxxxxxx
In [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.
xxxxxxxxxx
In [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 kaya
In [35]: mc.send(30)
received: 30
x
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 coro
In [41]: mc.send(2)
In [42]: mc.send(5)
coro2 received*2: 10
In [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