У овом упутству ћете научити како лако креирати итерације користећи Питхон генераторе, како се разликује од итератора и нормалних функција и зашто бисте га требали користити.
Видео: Питхон генератори
Генератори у Питхону
Пуно је посла на изградњи итератора у Питхону. Морамо да применимо класу са __iter__()
и __next__()
методом, да пратимо унутрашња стања и да подижемо StopIteration
када нема вредности које треба вратити.
Ово је и дуго и контраинтуитивно. Генератор долази у помоћ у таквим ситуацијама.
Питхон генератори су једноставан начин стварања итератора. Сав посао који смо горе поменули аутоматски обрађују генератори у Питхону.
Једноставно речено, генератор је функција која враћа објекат (итератор) преко којег можемо поновити (једну по једну вредност).
Креирајте генераторе у Питхону
Створити генератор у Питхону прилично је једноставно. Једноставно је као дефинисати нормалну функцију, али са yield
наредбом уместо са return
изјавом.
Ако функција садржи бар један yield
израз (може садржати и други yield
или return
исказ), она постаје функција генератора. Оба yield
и return
вратиће неку вредност из функције.
Разлика је у томе што док return
израз у потпуности завршава функцију, yield
израз зауставља функцију чувајући сва њена стања и касније наставља одатле на узастопне позиве.
Разлике између функције генератора и нормалне функције
Ево како се функција генератора разликује од нормалне функције.
- Функција генератора садржи један или више
yield
израза. - Када се позове, враћа објекат (итератор), али не започиње извршење одмах.
- Методе се свиђају
__iter__()
и__next__()
имплементирају се аутоматски. Тако можемо да прелиставамо ставке користећиnext()
. - Једном када функција попусти, функција се паузира и контрола се преноси позиваоцу.
- Локалне променљиве и њихова стања памте се између узастопних позива.
- Коначно, када се функција заврши,
StopIteration
аутоматски се покреће при даљим позивима.
Ево примера који илуструје све горе наведене тачке. Имамо функцију генератора именовану my_gen()
са неколико yield
израза.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
Интерактивни рад у преводиоцу дат је у наставку. Покрените их у Питхон љусци да бисте видели излаз.
>>> # It returns an object but does not start execution immediately. >>> a = my_gen() >>> # We can iterate through the items using next(). >>> next(a) This is printed first 1 >>> # Once the function yields, the function is paused and the control is transferred to the caller. >>> # Local variables and theirs states are remembered between successive calls. >>> next(a) This is printed second 2 >>> next(a) This is printed at last 3 >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls. >>> next(a) Traceback (most recent call last):… StopIteration >>> next(a) Traceback (most recent call last):… StopIteration
Једна занимљива ствар коју треба приметити у горњем примеру је да се вредност променљиве н памти између сваког позива.
За разлику од нормалних функција, локалне променљиве се не уништавају када функција попусти. Даље, објект генератора може се поновити само једном.
Да бисмо поново покренули процес, треба да креирамо још један генератор објекта користећи нешто слично a = my_gen()
.
Последња ствар коју треба приметити је да генераторе фор фор можемо користити директно.
То је зато што for
петља узима итератор и превлачи се преко њега помоћу next()
функције. Аутоматски се завршава када StopIteration
се подигне. Проверите овде да бисте знали како се петља фор заправо имплементира у Питхон.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop for item in my_gen(): print(item)
Када покренете програм, излаз ће бити:
Ово се прво штампа 1 Ово се штампа друго 2 Ово се штампа коначно 3
Питхон генератори са петљом
Горњи пример је од мање користи и ми смо га проучавали само да бисмо стекли представу о томе шта се дешавало у позадини.
Обично се функције генератора реализују са петљом која има одговарајуће завршно стање.
Узмимо пример генератора који обрће низ.
def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str(i) # For loop to reverse the string for char in rev_str("hello"): print(char)
Оутпут
оллех
У овом примеру смо користили range()
функцију за добијање индекса обрнутим редоследом помоћу петље фор.
Напомена : Ова функција генератора не ради само са низовима, већ и са другим врстама итерабила попут листе, корпице итд.
Израз Питхон Генератора
Једноставни генератори могу се лако створити у ходу помоћу израза генератора. То олакшава изградњу генератора.
Слично ламбда функцијама које стварају анонимне функције, изрази генератора стварају анонимне функције генератора.
Синтакса израза генератора слична је синтакси разумевања листе у Питхону. Али углате заграде замењују се округлим заградама.
Главна разлика између разумевања листе и израза генератора је у томе што разумевање листе ствара целу листу, док израз генератора производи једну по једну ставку.
Имају лење извршење (производе предмете само када их се тражи). Из тог разлога, израз генератора је много ефикаснији у меморији од еквивалентног разумевања листе.
# Initialize the list my_list = (1, 3, 6, 10) # square each term using list comprehension list_ = (x**2 for x in my_list) # same thing can be done using a generator expression # generator expressions are surrounded by parenthesis () generator = (x**2 for x in my_list) print(list_) print(generator)
Оутпут
(1, 9, 36, 100)
Горе можемо видети да израз генератора није одмах дао тражени резултат. Уместо тога, вратио је објекат генератора који производи предмете само на захтев.
Ево како можемо почети да добијамо ставке из генератора:
# Initialize the list my_list = (1, 3, 6, 10) a = (x**2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) next(a)
Када покренемо горњи програм, добијамо следећи излаз:
1 9 36 100 Трацебацк (последњи последњи позив): Датотека "", ред 15, у СтопИтератион
Изрази генератора могу се користити као аргументи функције. Када се користе на такав начин, округле заграде могу се испустити.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Употреба Питхон генератора
Постоји неколико разлога који генераторе чине моћном имплементацијом.
1. Једноставно за примену
Генератори се могу применити на јасан и концизан начин у поређењу са њиховим колегама класе итератора. Следи пример за примену низа снаге од 2 помоћу класе итератора.
class PowTwo: def __init__(self, max=0): self.n = 0 self.max = max def __iter__(self): return self def __next__(self): if self.n> self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
Горњи програм био је дуготрајан и збуњујући. Сада, учинимо исто користећи функцију генератора.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Будући да генератори аутоматски прате детаље, примена је била сажета и много чишћа.
2. Меморија ефикасна
Нормална функција за враћање секвенце креираће целу секвенцу у меморији пре враћања резултата. Ово је претјерано ако је број ставки у низу веома велик.
Имплементација генератора таквих секвенци прилагођена је меморији и пожељна је јер истовремено производи само један предмет.
3. Представљајте бесконачни ток
Генератори су изврсни медијуми који представљају бесконачан ток података. Бесконачни токови се не могу чувати у меморији, а будући да генератори производе само по једну ставку одједном, могу представљати бесконачан ток података.
Следећа функција генератора може генерисати све парне бројеве (барем у теорији).
def all_even(): n = 0 while True: yield n n += 2
4. Цевоводни генератори
Више генератора може се користити за цевовод низа операција. То је најбоље илустровано на примеру.
Претпоставимо да имамо генератор који производи бројеве у Фибоначијевој серији. И имамо још један генератор за квадрирање бројева.
Ако желимо да сазнамо зброј квадрата бројева у Фибоначијевој серији, то можемо учинити на следећи начин цевоводом излаза функција генератора.
def fibonacci_numbers(nums): x, y = 0, 1 for _ in range(nums): x, y = y, x+y yield x def square(nums): for num in nums: yield num**2 print(sum(square(fibonacci_numbers(10))))
Оутпут
4895
Овај цевовод је ефикасан и лак за читање (и да, пуно хладнији!).