В този урок ще научите как лесно да създавате итерации, използвайки генератори на Python, как се различава от итераторите и нормалните функции и защо трябва да го използвате.
Видео: Генератори на Python
Генератори в Python
Има много работа по изграждането на итератор в Python. Трябва да приложим клас с __iter__()
и __next__()
метод, да следим вътрешните състояния и да повишаваме, StopIteration
когато няма стойности, които да бъдат върнати.
Това е едновременно продължително и неинтуитивно. В такива ситуации на помощ идва генераторът.
Генераторите на Python са прост начин за създаване на итератори. Цялата работа, която споменахме по-горе, се обработва автоматично от генератори в Python.
Просто казано, генераторът е функция, която връща обект (итератор), който можем да повторим (по една стойност в даден момент).
Създаване на генератори в Python
Създаването на генератор в Python е доста просто. Това е толкова лесно, колкото дефинирането на нормална функция, но с 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
Интерактивно изпълнение в интерпретатора е дадено по-долу. Изпълнете ги в обвивката на Python, за да видите резултата.
>>> # 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
Едно интересно нещо, което трябва да се отбележи в горния пример е, че стойността на променлива n се запомня между всяко повикване.
За разлика от нормалните функции, локалните променливи не се унищожават, когато функцията дава. Освен това обектът генератор може да бъде повторен само веднъж.
За да рестартираме процеса, трябва да създадем друг обект на генератор, използвайки нещо като a = my_gen()
.
Едно последно нещо, което трябва да отбележим, е, че можем да използваме генератори с за цикли директно.
Това е така, защото for
цикълът взема итератор и се итерира върху него с помощта на next()
функция. Той автоматично приключва, когато StopIteration
бъде повдигнат. Проверете тук, за да разберете как всъщност се изпълнява цикъл for в Python.
# 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
Python генератори с цикъл
Горният пример е по-малко полезен и ние го изучихме само за да добием представа за случващото се на заден план.
Обикновено генераторните функции се изпълняват с контур, имащ подходящо крайно състояние.
Да вземем пример за генератор, който обръща низ.
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)
Изход
olleh
В този пример използвахме range()
функцията, за да получим индекса в обратен ред, използвайки цикъла for.
Забележка : Тази функция на генератор работи не само със низове, но и с други видове итерации като списък, кортеж и др.
Израз на генератор на Python
Простите генератори могат лесно да бъдат създадени в движение, като се използват изрази на генератори. Улеснява изграждането на генератори.
Подобно на ламбда функциите, които създават анонимни функции, генераторните изрази създават анонимни генераторни функции.
Синтаксисът за израз на генератор е подобен на този на разбирането на списък в Python. Но квадратните скоби се заменят с кръгли скоби.
Основната разлика между разбирането на списъка и израза на генератора е, че разбирането на списък създава целия списък, докато изразът на генератор създава по един елемент в даден момент.
Те имат мързеливо изпълнение (произвеждат предмети само когато са поискани). Поради тази причина генераторният израз е много по-ефективен от паметта, отколкото еквивалентното разбиране на списъка.
# 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 Traceback (последно последно обаждане): Файл "", ред 15, в StopIteration
Изразите на генератора могат да се използват като аргументи на функцията. Когато се използват по такъв начин, кръглите скоби могат да отпаднат.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Използване на генератори на Python
Има няколко причини, които правят генераторите мощно изпълнение.
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
Този конвейер е ефективен и лесен за четене (и да, много по-готин!).