# FIFO В Пайтоні (і в C#, і в кількох інших мовах) існує поняття "генератор" та "ітератор". Вони були придумані, щоб змінити спосіб, яким пишуться програми. Інтро, який я зроблю, буде основуватись на Факторіо, бо механізми конвеєрів дуже схожі на те, як працюють генератори "under the hood" ## Факторіо, генератори та пайплайни ### Producer, або початковий генератор ![](https://i.imgur.com/Qdx84zM.gif) Тут зображено механізм, який постійно викладає на конвеєр ресурси для сайенсу. В пайтоні ми би записали це як: ```python= import time while True: print("Copper wire") time.sleep(0.5) print("Iron plate") time.sleep(1) ``` Але що, якщо ми хочемо потім ці елементи якось обробляти? Давайте замість виводу на екран будемо додавати їх у список, і виділимо код у функцію: ```python= import time queue = [] def generate_resources(): while True: queue.append("Copper wire") time.sleep(0.5) queue.append("Iron plate") time.sleep(1) generate_resources() ``` Як тепер використати цю `queue` для постпроцесингу? Ніяк, бо функція зайшла у вічний цикл. Для вирішення цієї задачі потрібно змінити спосіб, яким уторвююється черга. Замість `queue.append` потрібно написати `yield`: ```python= ```python= import time def generate_resources(): while True: yield "Copper wire" time.sleep(0.5) yield "Iron plate" time.sleep(1) gen = generate_resources() ``` Після цього, через функцію `next` ми можемо отримувати елементи для черги: ```python [ins] In [537]: gen Out[537]: <generator object generate_resources at 0x7fb2d103b040> [ins] In [538]: next(gen) Out[538]: 'Copper wire' [ins] In [539]: next(gen) Out[539]: 'Iron plate' [ins] In [540]: next(gen) Out[540]: 'Copper wire' ``` Тобто, `yield` працює як `return` з функції, але який після `return` не закінчує функцію, а ставить "на паузу", і продовжує свою роботу після запуску `next(generator)`. ## Трансформер ![](https://i.imgur.com/ltpcT70.gif) Якщо використовувати генератори, то цей механізм з Факторіо можна записати так: ```python= import time def generate_items(): while True: yield "Iron gear" time.sleep(0.5) yield "Copper plate" time.sleep(1) def produce_science(items_queue): has_gear = False has_copper = False while True: res = next(items_queue) if res == "Iron gear": has_gear = True if res == "Copper plate": has_copper = True if has_gear and has_copper: yield "Red science pack" has_copper = False has_gear = False items = generate_items() science = produce_science(items) for item in science: print(item) ``` Хоч в кожній функції записано вічний цикл, за рахунок yield ми можемо управляти паузами в цих циклах. Цикл `for` в кінці буде проходити по всім елементам генератора і буде запускати всередині себе функцію `next()`. ## `yield from` Цей код ```python= def produce_list(): arr = list("hello world") for item in arr: yield item ``` можна записати коротше: ```python= def produce_list(): yield from list("hello world") ``` ## Завдання ### Генератор випадкових чисел `def gen_rand()` Потрібно зробити генератор (через yield), який буде постійно видавати випадкові числа. ### Генератор слів `def string_words(s)` Потрібно зробити генератор, який приймає на вхід рядок, а на вихід генерує (через yield) слова по порядку з цього рядку. ### Генератор рядків `def gen_file_lines(filename)` Потрібно зробити генератор, який приймає на вхід назву файлу, а на вихід генерує рядки з цього файлу, по одному рядку на yield. ### `def cycle(lst)` 'cycle' --- це операція, яка отримує на вхід список, наприклад, `[1,2,3]`, а на вихід видає числа з цього списку по кругу, `1, 2, 3, 1, 2, 3, 1, 2, 3, 1, ...` ### Батчинг `def batcher(gen, batch_size)` Потрібно написати генератор, який приймає на вхід генератор і число. Число --- це розмір батчу. Ця функція повинна збирати з генератора дані і пакувати їх у список (батч), і коли розмір батчу досягає відповідного числа, видавати на вихід (через yield) весь список. ### Перебір паролів `def brute_force(passlen)` Написати генератор, який буде перебирати всі паролі заданої довжини, і видавати їх на вихід через yield. ### Проксі принтер `def print_proxy(gen)` Написати функцію, яка отримує на вхід генератор, і все з нього виводить на екран. Але після виводу на екран вона вертає цей елемент через yield, щоб інші генератори могли підключатись до пайплайну. ### Прозорий запис у файл (проксі) `def file_proxy(gen, filename)` Написати функцію, яка отримує на вхід генератор, і записує все з нього у файл. Ця функція, хоч і зчитує з генератора, на вихід повинна вертати цей самий об'єкт, щоб інші фільтри і генератори могли підключатись до неї. Proxy --- це функція, яка прозоро працює всередині якогось пайплайну. ### Контент з папки `def gen_content(pathname)` Написати генератор, який приймає на вхід папку, а на вихід видає контент з цих файлів, по одному рядку на yield. ОБОВ'ЯЗКОВО використати попередньо написаний генератор рядків з файлу. ### Фільтр `def grep(gen, func)` Написати фільтр генератор, який буде залишати тільки ті об'єкти з генератора, які відповідають умові. Умова задається через `func` --- лямбда або окрема функція. ### Сокет проксі `def socket_proxy(gen, ip, port)` По прикладу файл проксі написати проксі генератор, який буде відправляти контент з генератору на віповідну IP адресу через механізм сокетів. ### Сокет сервер `def gen_socket(port)` Написати генератор, який буде видавати всі дані, які приходять на відповідний сокет порт з мережі. ### Moving average 'def moving_average(gen, lastN)' Написати генератор, який приймає на вхід числовий генератор, а на вихід видає через yield середнє значення останніх `lastN` чисел, згладжуючи результат. Параметр `lastN` називається "розмір вікна", а елементи з яких робиться середнє, називаються "вікном". ### `yield from` з затримкою `def gen_schedule(gen, frequency)` Потрібно зробити генератора, який буде видавати елементи з списку (або з іншого генератора) з заданою частотою. Тобто, ми можемо пинати цей генератор через `next()`, але він буде повертати `None`, і тільки якщо пройшов час генерації, то ми отримаємо на вихід значення. Всередині генератора не повинно бути `time.sleep()`, але можна робити часу заміри через `time.time()`. ### `@contenxtmanager` Прочитати про цей декоратор https://book.pythontips.com/en/latest/context_managers.html ![](https://i.imgur.com/1Z9TMBk.png) ### Ознайомитись з `itertools` Ознайомитись з стандартними функціями над ітераторами і генераторами: https://docs.python.org/3/library/itertools.html ## Advanced Ті, хто дійшов до цього моменту і зробив попередні завдання, ласкаво прошу до advanced рівня використання генераторів ![](https://i.imgur.com/agWkw9X.png) https://speakerdeck.com/dabeaz/generators-the-final-frontier