что такое генератор в python
Генераторы Python: что это такое и зачем они нужны
Генераторы используют, чтобы оперативная память не давилась большими объёмами информации. В Python это фишки, экономящие память.
Допустим, у вас есть файл, который весит десяток гигабайт. Из него нужно выбрать и обработать строки, подходящие под какое-то условие, а то и сравнить со строками другого большого файла.
Другой пример: нужно проанализировать практически бесконечный поток данных. Это могут быть, например, показания счётчиков, биржевые котировки, сетевой трафик.
А может, нужно создать поток данных самостоятельно: рассчитать комбинаторную структуру для определения вероятности какого-то события, математическую последовательность или последовательность случайных чисел.
Что делать? Хранить такие объёмы данных в компьютере нереально: они не поместятся в оперативную память — а некоторые и на жёсткий диск. Выход один — обрабатывать информацию небольшими порциями, чтобы не вызывать переполнения памяти. В Python на этот случай есть специальный инструмент — генераторы.
Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.
Что такое генератор и как он работает?
Этим генераторы отличаются от списков — те хранят в памяти все свои элементы, и удалить их можно только программно. Вычисления с помощью генераторов называются ленивыми, они экономят память.
Рассмотрим пример: создадим объект-генератор gen с помощью так называемого генераторного выражения. Он будет считать квадраты чисел от 1 до 4 — такую последовательность создаёт функция range(1,5).
Когда мы выведем на консоль переменную gen, то увидим лишь сообщение, что это объект-генератор.
При четырёх вызовах метода next(a) будут по одному рассчитываться и выводиться на консоль значения генератора: 1, 4, 9, 16. Причём в памяти будет сохраняться только последнее значение, а предыдущие сотрутся.
Когда мы попытаемся вызвать next(gen) в пятый раз, генератор сотрёт из памяти последний элемент (число 16) и выдаст исключение StopIteration.
Всё! Генератор больше не работает. Сколько бы мы ни вызывали next(gen), ничего считаться не будет. Чтобы запустить генератор ещё раз, придётся создавать его заново.
И что, для вычисления генератора придётся много раз вызывать next()?
Нет, значения можно вычислять в цикле for. В этом случае метод next() вызывается неявно. Например:
Когда весь цикл пройден, произойдёт исключение StopIteration. Хотя на консоль сообщение об этом не выводится, но генератор помнит о нём и больше работать не будет. То есть цикл for можно запускать только один раз, во второй раз не получится. Нельзя об этом забывать.
И чем помогут генераторы в наших задачах?
Для этого сначала рассмотрим упрощённый способ создания генератора — с помощью генераторного выражения.
Генераторные выражения позволяют создавать объект-генератор в одну строчку. В общем случае их пишут по шаблону:
( выражение for j in итерируемый объект if условие)
Где for, in, if — ключевые слова, j — переменная.
Пример генераторного выражения мы рассмотрели выше. Теперь посмотрим, как можно применить его для обработки большого файла.
Перед нами задача: на сервере есть огромный журнал событий log.txt, в котором хранятся сведения о работе какой-то системы за год. Из него нужно выбрать и обработать для статистики данные об ошибках — строки, содержащие слово error.
Такие строки можно выбрать и сохранить в памяти с помощью списка:
Здесь path — путь к файлу log. В результате сформируется список вида:
[строка1, строка2, строка3, ….. ]
В списке e_l содержатся все строки со словом error, они записаны в память компьютера. Теперь их можно обработать в цикле. Недостаток метода в том, что, если таких строк будет слишком много, они переполнят память и вызовут ошибку MemoryError.
Переполнения памяти можно избежать, если организовать поточную обработку данных с использованием объекта-генератора. Мы создадим его с помощью генераторного выражения (оно отличается от генератора списка только круглыми скобками).
Рассмотрим следующий код:
Этот метод не вызывает переполнения, так как в каждый момент времени в памяти находится только одна строка. При этом нужный для работы объём памяти не зависит от размера файла и количества строк, удовлетворяющих условию.
Как ещё можно создавать генераторы?
Генераторные выражения — это упрощённый вариант функций-генераторов, также создающих генераторы.
Функция-генератор отличается от обычной функции тем, что вместо команды return в ней используется yield. И если return завершает работу функции, то инструкция yield лишь приостанавливает её, при этом она возвращает какое-то значение.
При первом вызове метода next() выполняется код функции с первой команды до yield. При втором next() и последующих до конца генератора — код со следующей после yield команды и до тех пор, пока yield не встретится снова.
Чтобы было понятнее, рассмотрим небольшой пример:
Здесь функция f_gen(5) при вызове создаёт генератор a. Мы видим это, когда выводим a на консоль.
Посчитаем значения генератора в цикле for.
Как видим, значения переменных n и s между вызовами сохраняются.
Yield — инструмент очень гибкий. Его можно несколько раз использовать в коде функции-генератора. В этом случае команды yield служат разделителями кода: при первом вызове метода next() выполняется код до первого yield, при следующих вызовах — операторы между yield. При этом в генераторной функции необязательно должен быть цикл, все значения генератора и так посчитаются.
Как создать бесконечную последовательность
Рассмотрим, как можно с помощью генератора создать математическую последовательность, например, программу, генерирующую простые числа (напоминаем, это числа, не имеющие делителей, кроме 1).
Наша программа будет последовательно анализировать целые числа больше 1. Для каждого числа n программа ищет делители в диапазоне от 2 до √n. Если делители есть, программа переходит к следующему числу. Если их нет, значит, n — число простое, и программа выводит его на печать.
Этот код выдаёт бесконечную последовательность простых чисел без ограничения сверху. Остановить его можно только вручную.
Подобным образом с помощью генераторов можно создавать ряды случайных чисел, комбинаторные структуры, рекуррентные ряды, например, ряд Фибоначчи и другие последовательности.
Какие ещё методы есть у генераторов?
Когда-то был один next(), но в Python 2.5 появилось ещё три метода:
Рассмотрим пару небольших примеров.
С помощью этих методов можно создавать сопрограммы, или корутины, — это функции, которым можно передавать значения, приостанавливать и снова возобновлять их работу. Их обычно используют в Python для анализа потоков данных в корпоративной многозадачности. Генераторы позволяют создавать сложные разветвлённые программы для обработки потоков.
Что ещё можно сказать
С изучения генераторов начинается освоение последовательной обработки гигантских потоков данных. Это может быть, например, трейдинг и технический анализ в биржевых операциях.
Но даже если не говорить о глобальных задачах, скрипты с применением генераторов — это способ избежать копирования данных в память. Генераторы позволяют экономить ресурсы компьютера и создавать красивый чистый код.
Изучить генераторы и другие объекты Python можно на курсах в Skillbox. Вы получите серьёзные теоретические знания и практический опыт. С самого начала обучения будете участвовать в реальных проектах. Те, кто успешно окончит курсы, станут программистами middle-уровня, а мы поможем найти хорошую работу.
Подробно про генераторы Python – что такое и как работают
Что такое генераторы в Python?
Генераторы Python – это функции, которые возвращают объект обхода и используются для создания итераторов, просматривают сразу все элементы. Генератор также может быть выражением, синтаксис которого аналогичен пониманию списка в Python.
Создание итерации в Python сопряжено с большими трудностями; нам нужно реализовать методы __iter __() и __next __() для отслеживания внутренних состояний.
Создание итераторов – длительный процесс. Вот почему генератор играет важную роль в упрощении этого процесса. Если в итерации не найдено значение, возникает исключение StopIteration.
Как создать функцию генератора в Python?
Создать генератор на Python довольно просто. Он похож на обычную функцию, определяемую ключевым словом def, и использует ключевое слово yield вместо return. Или мы можем сказать, что если тело любой функции содержит оператор yield, он автоматически становится функцией-генератором. Рассмотрим следующий пример:
Yield вместо return
Оператор yield отвечает за управление потоком функции генератора. Он приостанавливает выполнение функции, сохраняя все состояния и уступая вызывающему. Позже он возобновляет выполнение при вызове следующей функции.
Оператор return возвращает значение и завершает работу всей функции, оператор return может использоваться в функции только один раз. Оператор yield в функции генератора мы можем использовать неоднократно.
Рассмотрим следующий пример.
Разница между функцией генератора и нормальной функцией
Генератор выражения
Мы можем легко создать выражение генератора без использования пользовательской функции. Это то же самое, что и лямбда-функция, которая создает анонимную функцию; выражения генератора создают анонимную функцию генератора.
Представление выражения генератора похоже на понимание списка Python. Единственное отличие состоит в том, что квадратные скобки заменены круглыми скобками. Понимание списка вычисляет весь список, тогда как выражение генератора вычисляет один элемент за раз.
Рассмотрим следующий пример:
В приведенной выше программе list comprehension вернуло список элементов в третьей степени, тогда как выражение генератора вернуло ссылку на вычисленное значение. Вместо применения цикла for мы также можем вызвать next() для объекта-генератора. Рассмотрим другой пример:
Примечание: – Когда мы вызываем next(), Python вызывает __next __() для функции, в которую мы передали его в качестве параметра.
В приведенной выше программе мы использовали функцию next(), которая вернула следующий элемент списка.
Пример программы для печати таблицы заданного числа с помощью генератора:
В приведенном выше примере функция генератора выполняет итерацию с использованием цикла for.
Преимущества
Есть различные преимущества генераторов. Некоторые из них приведены ниже:
Генераторы проще реализовать по сравнению с итератором. В итераторе мы должны реализовать функцию __iter __() и __next __().
Генераторы эффективно используют память для большого количества последовательностей. Обычная функция возвращает последовательность из списка, которая создает всю последовательность в памяти перед возвратом результата, а функция генератора вычисляет значение и приостанавливает их выполнение и возобновляется для следующего вызова.
Генератор бесконечной последовательности – отличный пример оптимизации памяти. Давайте обсудим это в приведенном ниже примере, используя функцию sys.getsizeof().
Из вышеприведенного вывода видно, что для list comprehension используется 4508 байт памяти, тогда как generator expression использует 56 байт памяти. Это означает, что объекты-генераторы намного эффективнее, чем сжатие списков.
Data Pipeline предоставляет возможность обрабатывать большие наборы данных или поток данных без использования дополнительной памяти компьютера.
Предположим, у нас есть файл журнала известного ресторана. В файле журнала есть столбец(4-й столбец), в котором отслеживается количество гамбургеров, проданных каждый час, и мы хотим просуммировать его, чтобы найти общее количество гамбургеров, проданных за 4 года. В этом сценарии генератор может создать конвейер с серией операций. Ниже приведен его код:
Генератор может производить бесконечное количество предметов. Бесконечные последовательности не могут содержаться в памяти, и поскольку генераторы производят только один элемент за раз, рассмотрим следующий пример:
В этом руководстве мы узнали о генераторах Python.
Функциональное программирование в Python. Генераторы, как питонячий декларативный стиль
Общее введение
Говоря о Python, обычно используется процедурный и ООП стиль программирования, однако это не значит, что другие стили невозможны. В презентации ниже мы рассмотрим ещё пару вариантов — Функциональное программирование и программирование с помощью генераторов. Последние, в том числе, привели к появлению сопрограмм, которые позднее помогли создать асинхронность в Python. Сопрограммы и асинхронность выходят за рамки текущего доклада, поэтому, если интересно, можете ознакомиться об этом самостоятельно. Лично я рекомендую книгу «Fluent Python», в которой разговор начинается от итераторов, плавно переходит в темы о генераторах, сопрограммах и асинхронности.
Введение в ФП
Говоря о ФП сразу следует подчеркнуть, что программирование через функции далеко не всегда ФП, чаще всего это всего лишь процедурный стиль программирования. Чтобы попробовать понять ФП — необходимо разобраться, что это такое, и помогут нам в этом теоретические знания.
Выделяют две крупные парадигмы программирования: императивная и декларативная.
Императивное программирование предполагает ответ на вопрос “Как?”. В рамках этой парадигмы вы задаете последовательность действий, которые нужно выполнить, для того чтобы получить результат. Результат выполнения сохраняется в ячейках памяти, к которым можно обратиться впоследствии.
Декларативное программирование предполагает ответ на вопрос “Что?”. Здесь вы описываете задачу, даете спецификацию, говорите, что вы хотите получить в результате выполнения программы, но не определяете, как этот ответ будет получен. Каждая из этих парадигм включает в себя более специфические модели.
В продуктовой разработке наибольшее распространение получили процедурное и объектно-ориентированное программирование из группы “императивное программирование” и функциональное программирование из группы “декларативное программирование”.
В рамках процедурного подхода к программированию основное внимание сосредоточено на декомпозиции – разбиении программы / задачи на отдельные блоки / подзадачи. Разработка ведётся пошагово, методом “сверху вниз”. Наиболее распространенным языком, который предполагает использование процедурного подхода к программирования является язык C, в нем, основными строительными блоками являются функции.
В рамках объектно-ориентированного (ООП) подхода программа представляется в виде совокупности объектов, каждый из которых является экземпляром определенного класса, классы образуют иерархию наследования. ООП базируется на следующих принципах: инкапсуляция, наследование, полиморфизм, абстракция. Примерами языков, которые позволяют вести разработку в этой парадигме являются C#, Java.
В рамках функционального программирования выполнение программы – процесс вычисления, который трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании). Языки, которые реализуют эту парадигму – Haskell, Lisp.
Языки, которые можно отнести в функциональной парадигме обладают определенным набором свойств. Если язык не является чисто функциональным, но реализует эти свойства, то на нем можно разрабатывать, как говорят, в функциональном стиле.
Основные принципы ФП
Функции высшего порядка принимают в качестве аргументов другие функции. В стандартную библиотеку Python входит достаточно много таких функций, в качестве примера приведем функцию map. Она принимает функцию и Iterable объект, применяет функцию к каждому элементу Iterable объекта и возвращает Iterator объект, который итеративно возвращает все модифицированные после функции элементы.
В Python это не выполняется. Необходимо самостоятельно следить за тем, чтобы функция была чистой.
Основные термины
Не все термины ниже необходимы для понимания доклада, но необходимы для понимания ФП (спасибо одному другу за их подборку)
Ссылочная прозрачность — свойство функциональных программ, в котором любое выражение может быть заменено вычисленным им значением без изменения поведения программы. Ссылочная прозрачность позволяет проводить рефакторинг программ без изменения их поведения.
Детерминированная функция возвращает тот же результат для одних и тех же входных данных.
Чистая функция трансформирует входные данные в выходные и не взаимодействует с миром вне функции каким-либо образом, который можно наблюдать. Все чистые функции детерминированы, но не все детерминированные функции чисты.
Тотальная функция возвращает вывод для каждого ввода.
Тотальные функции всегда завершаются и никогда не вызывают исключений.
Композиция функций — применение одной функции к результату другой
Сайд эффект возникает, когда выполнение выражения делает что-то большее, чем вычисление значения. Сайд эффекты — взаимодействия, которые можно наблюдать за пределами функции или выражения. Распространённые сайд эффекты включают в себя доступ к базе данных, доступ к файлам, доступ к сети, системные вызовы, изменение изменяемой памяти или вызов функций, которые выполняют любое из вышеперечисленных действий.
Полиморфизм — особенность языков программирования, которая позволяет переменной или функции принимать множество различных форм. Рассмотрим один из видов полиморфизма.
Параметрический полиморфизм, иногда называемый универсальным, является особенностью некоторых языков программирования, который позволяет универсально количественно определять функцию или тип данных по одному или нескольким параметрам типа. Такие полиморфные функции и полиморфные типы данных называются параметризованными их параметрами типа.
Параметрический полиморфизм позволяет создавать общий код, который работает со многими различными типами данных, и универсальными типами данных, таких как коллекции. Параметрически полиморфный код должен вести себя единообразно при любом выборе параметров типа, что дает мощный способ рассуждать о таком коде, называемый параметрическим рассуждением.
Замыкание — процедура вместе с привязанной к ней совокупностью данных. © Steve Majewski
Замыкание — функция, которая ссылается на свободные переменные в своей области видимости.
Встроенное ФП поведение в Python
В соответствии с вышесказанным, попробуем сделать несколько хаков, чтобы наш код был более ФПшный. Избавимся от if / elif / else в Python
Используем lambda для присваивания таких условных выражений
Замена циклов на выражения так же проста, как и замена условных блоков. for может быть переписана с помощью map().
То же самое мы можем сделать и с функциями.
Перевести while впрямую немного сложнее, но вполне получается.
ФП вариант while все еще требует функцию while_block(), которая сама по себе может содержать не только выражения, но и утверждения (statements). Но мы могли бы продолжить дальнейшее исключение утверждений в этой функции (как, например, замену блока if/else в вышеописанном шаблоне).
К тому же, обычная проверка на месте (наподобие while myvar == 7) вряд ли окажется полезной, поскольку тело цикла (в представленном виде) не может изменить какие-либо переменные (хотя глобальные переменные могут быть изменены в while_block()). Один из способов применить более полезное условие — заставить while_block() возвращать более осмысленное значение и сравнивать его с условием завершения.
Стоит взглянуть на реальный пример исключения утверждений:
Мы достигли того, что выразили небольшую программу, включающую ввод/вывод, циклы и условия в виде чистого выражения с рекурсией (фактически — в виде функционального объекта, который при необходимости может быть передан куда угодно).
Мы все еще используем служебную функцию monadic_print(), но эта функция совершенно общая и может использоваться в любых функциональных выражениях, которые мы создадим позже. Заметим, что любое выражение, содержащее monadic_print(x) вычисляется так же, как если бы оно содержало просто x.
После всей проделанной работы по избавлению от совершенно осмысленных конструкций и замене их на невразумительные вложенные выражения, возникает естественный вопрос — «Зачем?!». Перечитывая описания характеристик ФП, мы можем видеть, что все они достигнуты в Python. Но важнейшая (и, скорее всего, в наибольшей степени реально используемая) характеристика — исключение побочных эффектов или, по крайней мере, ограничение их применения специальными областями наподобие монад. Огромный процент программных ошибок и главная проблема, требующая применения отладчиков, случается из-за того, что переменные получают неверные значения в процессе выполнения программы.
Функциональное программирование обходит эту проблему, просто вовсе не присваивая значения переменным.
Секции, комментированные как #. more stuff. — места, где побочные эффекты с наибольшей вероятностью могут привести к ошибкам.
Очевидно, что инкапсуляция в функциях/объектах и тщательное управление областью видимости могут использоваться, чтобы защититься от этого рода проблем. Вы также можете всегда удалять (del) ваши переменные после использования.
Но на практике указанный тип ошибок весьма обычен. Функциональный подход к задаче полностью исключает ошибки, связанные с побочными эффектами. Возможное решение могло бы быть таким:
Реальное преимущество этого функционального примера в том, что в нем абсолютно ни одна переменная не меняет своего значения. Какое-либо неожиданное побочное влияние на последующий код (или со стороны предыдущего кода) просто невозможно. Конечно, само по себе отсутствие побочных эффектов не гарантирует безошибочность кода, но в любом случае это преимущество. Вместо вышеприведенных примеров — императивного или функционального — наилучшая (и функциональная) техника выглядит следующим образом:
Да, всё верно, list, tuple, set, dict comprehensions и generator expressions — изначально ФП техники, которые, как и многое другое, перекочевало в другие не-ФП языки
Библиотека Xoltar Toolkit
Сразу оговоримся, что библиотека достаточно старая и подходит лишь для Python 2, однако для ознакомления её достаточно. Библиотека Xoltar Toolkit Брина Келлера (Bryn Keller) покажет нам больше возможностей ФП.
Основные возможности ФП Келлер представил в виде небольшого эффективного модуля на чистом Python. Помимо модуля functional, в Xoltar Toolkit входит модуль lazy, поддерживающий структуры, вычисляемые «только когда это необходимо». Множество функциональных языков программирования поддерживают отложенное вычисление, поэтому эти компоненты Xoltar Toolkit предоставят вам многое из того, что вы можете найти в функциональном языке наподобие Haskell.
Ничто в Python не запрещает переприсваивания другого значения имени, ссылающемуся на функциональное выражение. В ФП под именами понимается всего лишь буквенное сокращение более длинных выражений, при этом подразумевается, что одно и то же выражение всегда приводит к одному и тому же результату. Если же уже определенному имени присваивается новое значение, это допущение нарушается.
К несчастью, одно и то же выражение sum2(range(10)) вычисляется к разным результатам в двух местах программы, несмотря на то, что аргументы выражении не являются изменяемыми переменными.
К счастью, модуль functional предоставляет класс Bindings, предотвращающий такое переприсваивание.
Разумеется, реальная программа должна перехватить и обработать исключение BindingError, однако сам факт его возбуждения позволяет избежать целого класса проблем.
Библиотека returns
Уже более современная библиотека, предлагаемая Никитой Соболевым, нашим соотечественником, также позволяет использовать возможности ФП в Python. Декоратор maybe позволяет переходить к следующей итерации только при успешном завершении предыдущей
Более реальный пример, из императивного стиля в декларативный можно переписать так:
Или, например, позволяет обходить возможные подводные камни напрямую через пайплайн
И то же самое, только с помощью returns
Теперь у нас есть чистый, безопасный и декларативный способ выразить потребности нашего бизнеса: Мы начинаем с запроса, который может потерпеть неудачу в любой момент, затем анализирем ответ, если запрос был успешным, а потом возвращаем результат.
Подробнее об этой библиотеке можно уточнить из её достаточно большой и подробной документации, указанной в литературе.
Прим. Я, как автор этой статьи, не использовал returns в проде и не думаю, что буду когда-либо использовать, но не упомянуть об этой библиотеке в рамках данной статьи просто нельзя.
Литература
Генераторы
Введение в итераторы
Итерация — по сути является перебором значений. Вот обычный пример, который встречается повсюду.
Также, как мы знаем, итерация может происходить по многим типам объектов (не только спискам). Причина, почему мы можем итерироваться по объектам — реализация специального протокола
Внутри под капотом обычной итерации.
Происходит примерно следующее:
Например, посмотрим, как имплементировать такое:
Его реализация будет такова:
Введение в генераторы
Генератор — функция, которая генерирует последовательность результатов вместо одного значения
Вместо того, чтобы возвращать значение, мы создаём серию значений (с использованием оператора yield). Вызов функции генератора создает объект-генератор. Однако функция не запускается.
Синтаксис генераторного выражения также прост
Генераторы vs итераторы
Функция генератор немного отличается от объекта, поддерживающего итерацию. Генератор — разовая операция. Мы можем перебирать сгенерированные данные один раз, но если мы хотим сделать это снова, мы должны снова вызвать функцию генератор. Это отличается, например, от списка (который мы можем перебирать столько раз, сколько хотим)
Генераторы как пайплайн
Теперь у нас есть два основных строительных блока, которые мы можем применить для создания пайплайна. Например, имеется задача:
Узнайте, сколько байтов данных было передано, суммируя последний столбец данных в журнале веб-сервера Apache. И да, размер лог файла может измеряться в гигабайтах
Каждая строка в логах выглядит примерно так:
Число байтов находится в последней колонке:
Это может быть число или отсутствующее значение.
Сконвертируем полученный результат в число
В итоге, может получиться примерно такой код
Мы читаем строка за строкой и обновляем сумму. Но это старый стиль. Посмотрим, как эту задачу можно решить с помощью генераторов.
Этот подход отличается от предыдущего, меньше строк, напоминает функциональный стиль Мы получили пайплайн
На каждом этапе конвейера мы объявляем операцию, которая будет применяться ко всему входному потоку. Вместо того чтобы сосредоточиться на проблеме построчно, мы просто разбиваем ее на большие операции, которые работают со всем файлом. Это декларативный подход.
Безусловно, этот генераторный подход имеет разновидности причудливой медленной магии. Файл из 1.3 ГБ код в старом стиле выполнил за 18.6 секунд, код на генераторах был выполнен за 16,7 секунд.
AWK с той же задачей справился гораздо медленее, за 70.5 секунд
Генераторное решение было основано на концепции конвейерной передачи данных между разными компонентами. Что, если бы у нас были более продвинутые виды компонентов для работы? Возможно, мы могли бы выполнять разные виды обработки, просто подключив различные компоненты друг за другом.
Концепт yield from
‘yield from’ может использоваться для делегирования итерации
Маршрутизация данных на генераторах (мультиплексирование, броадкастинг)
Задача — считать логи в режиме реального времени из разных источников, и транслировать результат нескольким потребителям
На данной диаграмме видно, что нам потребуется мультиплексирование (всё в одно) и броадкастинг (одно во всё). Что ж, не будем томить и напишем сразу решение.
Как мы видим из этого примера — ничего сложного в этом нет, вполне легко можно решать самые разнообразные задачи.
Пример трейсинга генератора
Тут хотелось бы показать достаточно простой пример, если у тебя используется сотня генераторов и не понятно, в каком из них происходит сбой, например, с помощью принта.
Как мы видим — такой дебаг генератор можно встроить в любой из генераторов, не изменяя его, получив необходимую информацию, например, выведя результат каждый итерации
Стандартные инструменты генераторы
Выводы
Плюсы:
Минусы
Литература
Итоги
В ООП и ФП есть как свои плюсы, так и свои минусы. Например, на чистом ФП не напишешь красивый и выразительный код на Python, в котором можно легко разобраться, из-за чего преимущества Python сходят на нет. Однако, это не значит, что мы не можем использовать преимущества функционального подхода.
Как можно заметить, если мы пишем код на генераторах, то он получается вполне себе декларативным. Можно сказать, что код на генераторах — в некоторой степени ФП, так элегантно вошедший в Python.
Наша главная задача — писать ясный, понятный, красивый, тестируемый код и выбирать для этого подходящие инструменты. ФП — не самоцель, а лишь средство, как и всегда, чтобы мочь написать ещё лучший код!