Итераторы и Iterables в моем рабочем процессе обработки данных Python

Большую часть своей карьеры в науке о данных я не оптимально использовал язык программирования Python. Честно говоря, формально я никогда не изучал Python, поэтому мои первоначальные проекты были скорее хаком, чем структурным мышлением. В последние несколько дней я решил пересмотреть некоторые из своих старых проектов на Python и внедрить в них лучшие идеи программирования. Одна ошибка, которую я постоянно замечал, — это неправильное использование итерируемых объектов в коде. Этот блог представляет собой беседу об этих двух фундаментальных концепциях Python — Iterators и Iterables.

Сумма факториала

В качестве рабочего примера давайте подумаем о программе, которая суммирует факториал первых n целых чисел. Это немного надуманный пример, но этот шаблон довольно распространен во многих приложениях для обработки данных, которые я видел. Если вы чем-то похожи на меня, вы написали программу, подобную приведенной ниже.

Вы сразу заметите проблему с этой программой. Мы пересчитываем factorial(1) to factorial(n-1) для вычисления factorial(n). Таким образом, умный ридер перепишет специализированную функцию, которая станет эффективной.

Однако при программировании в функциональном стиле следует по возможности избегать специализированных функций. Специализированная функция fact_sum(n) не может использоваться ни в какой другой программе, в которой требуется факториал. Как специалист по данным, большая часть кода, который я пишу, написана в функциональном стиле, и я хотел бы избежать специализации функций. Для более систематического обзора функционального программирования ознакомьтесь с разделом Это как.

Итерации и итераторы

Альтернативный подход здесь заключается в разработке Iterable и использовании Iterator для получения факториалов. Большинство из нас использовали итераторы в каждой программе, написанной на Python. Название этого сообщения for i in range(n): является одним из таких примеров. Функция Range возвращает итератор. В приведенном ниже коде показан шаблон итератора для нашей факториальной задачи. В этом примере мы создаем Iterable, реализуя методы __iter__ и __next__.

Прежде чем кто-либо из вас начнет жаловаться, что это ужасный шаблон для реализации Iterable и Iterator над самим собой, позвольте мне заявить, что я согласен. Я немного машу рукой, потому что это редко используемый шаблон в рабочих процессах Data Science. Тем не менее, это важная идея. Итак, прежде чем я перейду к генераторам, позвольте мне разобрать приведенный выше код. Реализуя эти специальные методы, мы создали для Python механизм использования итератора facts в цикле for, как показано в примере ниже for f in facts. Также обратите внимание на использование StopIteration в качестве сигнала о том, что у итератора больше нет информации для отправки. Все встроенные модули Python имеют изящный способ обработки StopIteration. Таким образом, цикл for точно знает, что делать с StopIteration. Я хотел бы обратиться к документации Python для получения дополнительной информации об Iterables и Iterators.

Генераторы

Генераторы — это наиболее часто используемый механизм итерации в рабочих процессах обработки данных. Генератор определяется как функция с использованием ключевого слова def. Но вместо возврата значения он дает! Давайте посмотрим на наш факторный пример. Функция генератора создает объект генератора my_iter, который является оболочкой для тела функции. Каждый раз, когда мы вызываем next(...) (следующий вызывается циклом for) для объекта-генератора, вызов оценивает функцию до yield, а затем функция приостанавливается. Это позволяет состоянию сохраняться, и мы получаем один факториал за другим без повторного вычисления каких-либо ранее вычисленных значений. Объекты-генераторы следуют тем же протоколам, что и итераторы — они вызывают StopIteration один раз тело функции returns.

Интересное замечание об использовании def для получения результатов — factorial2 не является функцией, но мы используем ключевое слово def. Кажется, были дебаты о том, следует ли использовать ключевое слово def или использовать новое. В PEP 255 обсуждались плюсы и минусы, и, наконец, BDFL (Гвидо) принял окончательное решение.

деф он остается. Никакие аргументы ни с одной из сторон не являются полностью убедительными, поэтому я обратился к своей интуиции дизайнера языка. Это говорит мне, что синтаксис, предложенный в PEP, совершенно правильный — не слишком горячий, не слишком холодный. Но, подобно Оракулу в Дельфах в греческой мифологии, он не говорит мне, почему, поэтому у меня нет опровержений аргументов против синтаксиса PEP. Лучшее, что я могу придумать (помимо согласия с уже сделанными опровержениями), это «FUD». Если бы это было частью языка с самого первого дня, я очень сомневаюсь, что это попало бы на страницу Эндрю Кучлинга «Python Warts».

Itertools и Functools

В заключительном разделе этого блога мы поговорим об Itertools. Обычно нам не нужно создавать собственные итераторы или даже генераторы для большинства приложений. Itertools и Functools имеют довольно много хороших функций, которые делают все это за нас. Две наиболее часто используемые функции из этого модуля — zip и enumerate. Но есть и другие, особенно примечательные — count(), accumulate(), chain(), filter() и многое другое.

Наш последний фрагмент кода реализует код факториала с помощью itertools.

Вот и все!

Вывод

Я надеюсь, что это дало небольшое представление о том, как итерации работают в Python. Большинство опытных питонистов знают об этом шаблоне. Но если вы похожи на меня и не знаете всех подробностей, надеюсь, мне удалось пролить свет на мир итераторов, итерируемых объектов и генераторов.