generator - це функція або вираз, що повертає особливий тип ітератора, який називається generator iterator.
Generator-iterator-и ліниві: вони не зберігають свої values у памʼяті, а генерують свої значення тоді, коли вони потрібні.
Функція-генератор має такий самий вигляд, як і будь-яка інша функція, але містить один або кілька виразів yield.
Кожен yield призупиняє виконання коду, зберігаючи поточний стан виконання (разом з усіма локальними змінними та інструкціями try).
Коли генератор відновлює роботу, він повертається до збереженого стану, на відміну від звичайних функцій, які скидають стан за кожного виклику.
Генератори створюють майже так само, як інші функції з циклами чи рекурсією, але вони потребують виразу yield, який ми трохи згодом розглянемо докладніше.
Ось приклад: функція, що повертає квадрати чисел із заданого масиву. У поточному вигляді всі вхідні дані потрібно обробити, перш ніж повернути хоч якісь значення:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
Перетворити цю функцію на генератор можна так:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
Сенс у тому, що генератор використовують, коли не потрібно отримувати всі значення одразу. Це заощаджує памʼять і обчислювальні ресурси, адже обчислюється лише те значення, з яким ми працюємо в цей момент.
У Python генератори можна використовувати замість більшості iterables.
Це стосується функцій та обʼєктів, які приймають iterable/iterator як аргумент.
Щоб скористатися генератором squares_generator():
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
Значення з generator можна також отримувати й читати за допомогою функції next().
next() викликає метод __next__() обʼєкта-генератора-ітератора, «просуваючи» або обчислюючи код до його виразу yield, який потім «віддає» чи повертає значення:
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
Коли generator-iterator повністю вичерпано й більше немає значень для повернення, він викидає помилку StopIteration.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Генератори-ітератори - це особливий підвид ітераторів.
Iterators - це механізм чи протокол, який уможливлює перебирання iterables.
Генератори-ітератори та ітератори, які повертають звичайні Python-iterables, поводяться дуже схоже, але варто звернути увагу на кілька важливих відмінностей:
indexes.
До попереднього чи наступного значення не можна дістатися додаванням чи відніманням, а також не можна використовувати запис із квадратними дужками ([]) чи зрізи.len(), бо вони не мають довжини.generator-iterator!yield
Вираз yield дуже схожий на вираз return.
На відміну від виразу return, yield віддає значення тому, хто викликав, у певній точці, призупиняючи обчислення й повернення решти значень, доки їх не запросять.
Коли yield обчислюється, він зупиняє виконання функції, у якій міститься, і повертає значення цієї функції на той момент часу.
Після цього функція залишається в області видимості, і коли викликають __next__(), виконання продовжується, доки yield не зустрінеться знову.
Використовувати вирази yield поза функціями заборонено.
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
Генератори стають у пригоді в багатьох задачах.
Коли ми працюємо з великим набором значень, класти їх усі в памʼять, імовірно, не варто. Генератор дає змогу обробляти великі дані частина за частиною, заощаджуючи памʼять і підвищуючи швидкодію.
Генератори також дуже помічні, коли процес чи обчислення складні, затратні або нескінченні:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
Тепер, коли для обʼєкта infinite_sequence викликають __next__(), він повертатиме попереднє число + 1.
Conda Airlines - найбільша авіакомпанія у світі програмування, що виконує понад 10 000 рейсів щодня!
Зараз вони призначають усі місця пасажирам вручну; це потрібно автоматизувати.
Вони попросили нас створити програмне забезпечення, яке автоматизує призначення місць пасажирам. Вони вимагають, щоб наше програмне забезпечення економно витрачало памʼять і працювало швидко.
Conda хоче генерувати літери місць для своїх літаків.
Літак складається з рядів місць.
У кожному ряду 4 місця.
Місця в кожному ряду завжди мають назви A, B, C і D.
Перше місце в ряду - A, друге місце в ряду - B, і так далі.
Після D усе починається знову з A.
Реалізуйте функцію generate_seat_letters(<number>), яка приймає int, у якому записано, скільки літер місць потрібно згенерувати.
Тоді функція має повернути ітерабельний обʼєкт з літер місць.
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
Conda хоче систему, яка може згенерувати задану кількість місць для своїх літаків.
У кожного літака 4 місця в кожному ряду.
Ряди задаються числами, починаючи з 1 і далі.
Місця мають бути впорядковані так: 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...
Ось приклад:
| x | 1 | 2 |
|---|---|---|
| Ряд | 5 | 21 |
| Літера місця | A | D |
| Результат | 5A | 21D |
Через забобони серед пасажирів багато авіакомпаній не використовують ряд номер 13 на своїх рейсах. Conda Airlines також дотримується цієї традиції, тож подбайте, щоб ми не генерували місця для ряду номер 13.
Реалізуйте функцію generate_seats(<number>), яка приймає int, у якому записано, скільки місць потрібно згенерувати.
Тоді функція має повернути ітерабельний обʼєкт із цими місцями.
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
Тепер, коли у нас є функція, яка генерує місця, ми можемо використати її, щоб призначити місця пасажирам.
Реалізуйте функцію assign_seats(<passengers>), яка приймає list з іменами пасажирів.
Тоді функція має повернути словник, де passenger - ключ, а seat_number - значення.
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
Conda Airlines хотіла б мати унікальний код для кожного квитка. Оскільки це велика авіакомпанія, у неї багато рейсів. Це означає, що є багато рейсів з однаковим номером місця. Вони хочуть, щоб ми створили систему, яка формує унікальний квиток - рядок тексту (англ. string) завдовжки 12 символів.
Цей код починається з assigned_seat, за яким іде flight_id.
Решту коду заповнюють 0s.
Реалізуйте функцію generate_codes(<seat_numbers>, <flight_id>), яка приймає list із seat_numbers і string з номером рейсу.
Тоді функція має повернути generator, який видає ticket_number.
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'
Зареєструйтеся на Exercism, щоб вивчати й опановувати Python, а також 17 концепцій146 вправ та справжнє наставництво від людей, і все це безкоштовно.