У цьому підручнику ви дізнаєтеся, як легко створювати ітерації за допомогою генераторів 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 для циклів безпосередньо.
Це тому, що 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)
Вихідні дані
олех
У цьому прикладі ми використовували range()
функцію для отримання індексу в зворотному порядку за допомогою циклу for.
Примітка : Ця функція генератора працює не тільки з рядками, але і з іншими видами ітерацій, такими як list, кортеж тощо.
Вираз генератора Python
Прості генератори можна легко створити на льоту, використовуючи вирази генератора. Це полегшує побудову генераторів.
Подібно до лямбда-функцій, які створюють анонімні функції, вирази генератора створюють анонімні функції генератора.
Синтаксис виразу генератора подібний до синтаксису розуміння списку в Python. Але квадратні дужки замінюються круглими дужками.
Основна різниця між розумінням списку та виразом генератора полягає в тому, що розуміння списку створює весь список, тоді як вираз генератора створює по одному елементу за раз.
They have lazy execution ( producing items only when asked for ). For this reason, a generator expression is much more memory efficient than an equivalent list comprehension.
# 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)
Output
(1, 9, 36, 100)
We can see above that the generator expression did not produce the required result immediately. Instead, it returned a generator object, which produces items only on demand.
Here is how we can start getting items from the generator:
# 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)
When we run the above program, we get the following output:
1 9 36 100 Traceback (most recent call last): File "", line 15, in StopIteration
Generator expressions can be used as function arguments. When used in such a way, the round parentheses can be dropped.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Use of Python Generators
There are several reasons that make generators a powerful implementation.
1. Easy to Implement
Generators can be implemented in a clear and concise way as compared to their iterator class counterpart. Following is an example to implement a sequence of power of 2 using an iterator class.
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
The above program was lengthy and confusing. Now, let's do the same using a generator function.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Since generators keep track of details automatically, the implementation was concise and much cleaner.
2. Memory Efficient
A normal function to return a sequence will create the entire sequence in memory before returning the result. This is an overkill, if the number of items in the sequence is very large.
Generator implementation of such sequences is memory friendly and is preferred since it only produces one item at a time.
3. Represent Infinite Stream
Генератори - чудові середовища для представлення нескінченного потоку даних. Нескінченні потоки не можуть зберігатися в пам'яті, і оскільки генератори виробляють лише один елемент за раз, вони можуть представляти нескінченний потік даних.
Наступна функція генератора може генерувати всі парні числа (принаймні теоретично).
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
Цей конвеєр ефективний і легко читається (і так, набагато крутіший!).