Доходност на Python, генератори и изрази на генератора

В този урок ще научите как лесно да създавате итерации, използвайки генератори на 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

Този конвейер е ефективен и лесен за четене (и да, много по-готин!).

Интересни статии...