functional.md
1> **Источник:** https://python-all.ru/2.7/howto/functional.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Руководство по функциональному программированию89**Автор**1011A. M. Kuchling1213**Версия**14150.311617В этом документе мы рассмотрим возможности Python, подходящие для реализации программ в функциональном стиле. После введения в концепции функционального программирования мы рассмотрим такие языковые средства, как [итератор](https://python-all.ru/2.7/glossary.html#term-iterator)ы и [генератор](https://python-all.ru/2.7/glossary.html#term-generator)ы, а также соответствующие библиотечные модули, такие как [`itertools`](https://python-all.ru/2.7/library/itertools.html#module-itertools) и [`functools`](https://python-all.ru/2.7/library/functools.html#module-functools).1819## Введение2021В этом разделе объясняется основная концепция функционального программирования; если требуется изучить только возможности языка Python, можно пропустить его и перейти к следующему разделу.2223Языки программирования поддерживают декомпозицию задач несколькими различными способами:2425- Большинство языков программирования являются **процедурными**: программы представляют собой списки инструкций, которые указывают компьютеру, что делать с входными данными программы. C, Pascal и даже оболочки Unix являются процедурными языками.26- В **декларативных** языках пишется спецификация, описывающая решаемую задачу, а реализация языка самостоятельно определяет, как эффективно выполнить вычисления. SQL – это декларативный язык, с которым вы, скорее всего, знакомы; SQL-запрос описывает набор данных, который вы хотите получить, а механизм SQL решает, сканировать таблицы или использовать индексы, какие подзапросы выполнять в первую очередь и т.д.27- **Объектно-ориентированные** программы манипулируют коллекциями объектов. Объекты имеют внутреннее состояние и поддерживают методы, которые запрашивают или изменяют это внутреннее состояние определённым образом. Smalltalk и Java являются объектно-ориентированными языками. C++ и Python – языки, поддерживающие объектно-ориентированное программирование, но не навязывающие использование объектно-ориентированных возможностей.28- **Функциональное** программирование разбивает задачу на набор функций. В идеале функции только принимают входные данные и выдают результаты, не имея внутреннего состояния, влияющего на вывод для заданного входа. К известным функциональным языкам относятся семейство ML (Standard ML, OCaml и другие варианты) и Haskell.2930Разработчики некоторых языков программирования выбирают акцент на одном конкретном подходе к программированию. Это часто затрудняет написание программ, использующих другой подход. Другие языки являются мультипарадигмальными и поддерживают несколько различных подходов. Lisp, C++ и Python – мультипарадигмальные; на всех этих языках можно писать программы или библиотеки, которые в значительной степени являются процедурными, объектно-ориентированными или функциональными. В большой программе разные разделы могут быть написаны с использованием разных подходов; например, графический интерфейс может быть объектно-ориентированным, а логика обработки – процедурной или функциональной.3132В функциональной программе входные данные проходят через набор функций. Каждая функция обрабатывает свой вход и выдаёт некоторый результат. Функциональный стиль не одобряет функции с побочными эффектами, изменяющими внутреннее состояние или вносящими другие изменения, невидимые в возвращаемом значении функции. Функции, не имеющие побочных эффектов вообще, называются **чисто функциональными**. Избегание побочных эффектов означает отказ от использования структур данных, которые обновляются в процессе выполнения программы; результат каждой функции должен зависеть только от её входных данных.3334Некоторые языки очень строги в отношении чистоты и даже не имеют операторов присваивания, таких как `a=3` или `c = a + b`, но избежать всех побочных эффектов сложно. Например, вывод на экран или запись в файл на диске – это побочные эффекты. Например, в Python оператор `print` или `time.sleep(1)` не возвращают полезного значения; они вызываются только ради побочных эффектов: отправки текста на экран или приостановки выполнения на секунду.3536Программы на Python, написанные в функциональном стиле, обычно не доходят до крайности полного отказа от ввода-вывода или присваиваний; вместо этого они предоставляют интерфейс, выглядящий функциональным, но внутри используют не функциональные возможности. Например, реализация функции всё равно будет использовать присваивания локальным переменным, но не будет изменять глобальные переменные или иметь другие побочные эффекты.3738Функциональное программирование можно считать противоположностью объектно-ориентированного. Объекты – это небольшие капсулы, содержащие некоторое внутреннее состояние вместе с набором вызовов методов, позволяющих изменять это состояние, а программы состоят из выполнения правильного набора изменений состояния. Функциональное программирование стремится максимально избегать изменений состояния и работает с данными, передаваемыми между функциями. В Python вы можете комбинировать эти два подхода, написав функции, которые принимают и возвращают экземпляры, представляющие объекты в вашем приложении (сообщения электронной почты, транзакции и т.д.).3940Функциональный дизайн может показаться странным ограничением для работы. Зачем избегать объектов и побочных эффектов? У функционального стиля есть теоретические и практические преимущества:4142- Формальная доказуемость.43- Модульность.44- Композируемость.45- Лёгкость отладки и тестирования.4647### Формальная доказуемость4849Теоретическое преимущество заключается в том, что легче построить математическое доказательство корректности функциональной программы.5051Уже долгое время исследователи заинтересованы в поиске способов математического доказательства корректности программ. Это отличается от тестирования программы на множестве входных данных и заключения, что её вывод обычно корректен, или чтения исходного кода программы и заключения, что код выглядит правильно; цель – строгое доказательство того, что программа выдаёт правильный результат для всех возможных входных данных.5253Метод, используемый для доказательства корректности программ, заключается в записи **инвариантов** – свойств входных данных и переменных программы, которые всегда истинны. Для каждой строки кода затем показывается, что если инварианты X и Y истинны **до** выполнения строки, то слегка изменённые инварианты X’ и Y’ истинны **после** выполнения строки. Это продолжается до конца программы, после чего инварианты должны соответствовать желаемым условиям на выход программы.5455Избегание присваиваний в функциональном программировании возникло потому, что присваивания сложно обрабатывать с помощью этого метода; присваивания могут нарушить инварианты, которые были истинны до присваивания, не порождая новых инвариантов, которые можно было бы распространить дальше.5657К сожалению, доказательство корректности программ в значительной степени непрактично и не применимо к программному обеспечению на Python. Даже тривиальные программы требуют доказательств длиной в несколько страниц; доказательство корректности для умеренно сложной программы было бы огромным, и мало какие (или вообще никакие) из программ, которые вы используете ежедневно (интерпретатор Python, ваш XML-парсер, веб-браузер), могли бы быть доказаны корректными. Даже если бы вы записали или сгенерировали доказательство, возник бы вопрос его проверки; возможно, в нём есть ошибка, и вы ошибочно полагаете, что доказали корректность программы.5859### Модульность6061Более практическое преимущество функционального программирования заключается в том, что оно заставляет разбивать задачу на небольшие части. Программы становятся более модульными. Проще определить и написать маленькую функцию, которая делает одно дело, чем большую функцию, выполняющую сложное преобразование. Маленькие функции также легче читать и проверять на ошибки.6263### Лёгкость отладки и тестирования6465Тестировать и отлаживать программу в функциональном стиле проще.6667Отладка упрощается, потому что функции обычно малы и чётко определены. Когда программа не работает, каждая функция является точкой интерфейса, где можно проверить корректность данных. Можно просмотреть промежуточные входные и выходные данные, чтобы быстро изолировать функцию, ответственную за ошибку.6869Тестирование проще, потому что каждая функция является потенциальным объектом для модульного теста. Функции не зависят от состояния системы, которое нужно воспроизводить перед запуском теста; вместо этого нужно только синтезировать правильный вход и затем проверить, что выход соответствует ожиданиям.7071### Композируемость7273В процессе работы над программой в функциональном стиле вы напишете ряд функций с различными входными и выходными данными. Некоторые из этих функций будут неизбежно специализированы для конкретного приложения, но другие будут полезны в самых разных программах. Например, функция, принимающая путь к каталогу и возвращающая все XML-файлы в нём, или функция, принимающая имя файла и возвращающая его содержимое, могут применяться во многих различных ситуациях.7475Со временем у вас сформируется личная библиотека утилит. Часто вы будете собирать новые программы, компонуя существующие функции в новой конфигурации и написав несколько функций, специализированных для текущей задачи.7677## Итераторы7879Я начну с рассмотрения возможности языка Python, которая является важной основой для написания функциональных программ: итераторов.8081Итератор – это объект, представляющий поток данных; этот объект возвращает данные по одному элементу за раз. Итератор Python должен поддерживать метод `next()`, который не принимает аргументов и всегда возвращает следующий элемент потока. Если в потоке больше нет элементов, `next()` должен возбуждать исключение `StopIteration`. Однако итераторы не обязательно должны быть конечными; вполне допустимо написать итератор, который порождает бесконечный поток данных.8283Встроенная функция [`iter()`](https://python-all.ru/2.7/library/functions.html#iter) принимает произвольный объект и пытается вернуть итератор, который будет возвращать содержимое или элементы объекта, возбуждая [`TypeError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.TypeError), если объект не поддерживает итерацию. Некоторые встроенные типы данных Python поддерживают итерацию, наиболее распространёнными из которых являются списки и словари. Объект называется **итерируемым**, если для него можно получить итератор.8485Можно поэкспериментировать с интерфейсом итерации вручную:8687```python88>>> L = [1,2,3]89>>> it = iter(L)90>>> print it91<...iterator object at ...>92>>> it.next()93194>>> it.next()95296>>> it.next()97398>>> it.next()99Traceback (most recent call last):100 File "<stdin>", line 1, in <module>101StopIteration102>>>103```104105Python ожидает итерируемые объекты в нескольких различных контекстах, наиболее важным из которых является оператор `for`. В операторе `for X in Y` Y должен быть итератором или объектом, для которого `iter()` может создать итератор. Эти два оператора эквивалентны:106107```python108for i in iter(obj):109 print i110111for i in obj:112 print i113```114115Итераторы можно материализовать в списки или кортежи с помощью функций-конструкторов `list()` или [`tuple()`](https://python-all.ru/2.7/library/functions.html#tuple):116117```python118>>> L = [1,2,3]119>>> iterator = iter(L)120>>> t = tuple(iterator)121>>> t122(1, 2, 3)123```124125Распаковка последовательностей также поддерживает итераторы: если известно, что итератор вернёт N элементов, их можно распаковать в N-кортеж:126127```python128>>> L = [1,2,3]129>>> iterator = iter(L)130>>> a,b,c = iterator131>>> a,b,c132(1, 2, 3)133```134135Встроенные функции, такие как [`max()`](https://python-all.ru/2.7/library/functions.html#max) и [`min()`](https://python-all.ru/2.7/library/functions.html#min), могут принимать один аргумент-итератор и возвращать наибольший или наименьший элемент. Операторы `"in"` и `"not in"` также поддерживают итераторы: `X in iterator` истинно, если X найден в потоке, возвращаемом итератором. С бесконечным итератором возникнут очевидные проблемы: `max()` и `min()` никогда не завершатся, а если элемент X никогда не появится в потоке, операторы `"in"` и `"not in"` тоже не вернут результат.136137Обратите внимание, что итератор можно обходить только вперёд; нет способа получить предыдущий элемент, сбросить итератор или сделать его копию. Объекты-итераторы могут опционально предоставлять эти дополнительные возможности, но протокол итератора определяет только метод `next()`. Поэтому функции могут потребить весь вывод итератора, и если потребуется сделать что-то другое с тем же потоком, придётся создать новый итератор.138139### Типы данных, поддерживающие итераторы140141Мы уже видели, как списки и кортежи поддерживают итераторы. На самом деле любой тип последовательности Python, например строки, автоматически поддерживает создание итератора.142143Вызов [`iter()`](https://python-all.ru/2.7/library/functions.html#iter) для словаря возвращает итератор, который будет перебирать ключи словаря:144145```python146>>> m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,147... 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}148>>> for key in m:149... print key, m[key]150Mar 3151Feb 2152Aug 8153Sep 9154Apr 4155Jun 6156Jul 7157Jan 1158May 5159Nov 11160Dec 12161Oct 10162```163164Порядок по существу случаен, так как он основан на хеш-упорядочении объектов в словаре.165166Применение `iter()` к словарю всегда выполняет итерацию по ключам, но у словарей есть методы, возвращающие другие итераторы. Если нужно выполнить итерацию по ключам, значениям или парам ключ/значение, можно явно вызвать методы `iterkeys()`, `itervalues()` или `iteritems()`, чтобы получить соответствующий итератор.167168Конструктор [`dict()`](https://python-all.ru/2.7/library/stdtypes.html#dict) может принимать итератор, возвращающий конечный поток кортежей `(key, value)`:169170```python171>>> L = [('Italy', 'Rome'), ('France', 'Paris'), ('US', 'Washington DC')]172>>> dict(iter(L))173{'Italy': 'Rome', 'US': 'Washington DC', 'France': 'Paris'}174```175176Файлы также поддерживают итерацию путём вызова метода `readline()` до тех пор, пока в файле не останется строк. Это означает, что можно читать каждую строку файла следующим образом:177178```python179for line in file:180 # сделать что-то для каждой строки181 ...182```183184Множества могут принимать своё содержимое из итерируемого объекта и позволяют перебирать элементы множества:185186```python187S = set((2, 3, 5, 7, 11, 13))188for i in S:189 print i190```191192## Генераторные выражения и списковые включения193194Две распространённые операции над выводом итератора: 1) выполнение некоторого действия для каждого элемента, 2) выбор подмножества элементов, удовлетворяющих некоторому условию. Например, имея список строк, можно удалить завершающие пробелы из каждой строки или извлечь все строки, содержащие заданную подстроку.195196Списковые включения и генераторные выражения (сокращённо «listcomps» и «genexps») – это краткая запись для таких операций, заимствованная из функционального языка программирования Haskell ([https://www.haskell.org/](https://python-all.ru/2.7/howto/functional.html)). Удалить все пробелы из потока строк можно следующим кодом:197198```python199line_list = [' line 1\n', 'line 2 \n', ...]200201# Генераторное выражение – возвращает итератор202stripped_iter = (line.strip() for line in line_list)203204# Списочное выражение – возвращает список205stripped_list = [line.strip() for line in line_list]206```207208Можно выбрать только определённые элементы, добавив условие `"if"`:209210```python211stripped_list = [line.strip() for line in line_list212 if line != ""]213```214215При использовании спискового включения возвращается список Python; `stripped_list` – это список, содержащий результирующие строки, а не итератор. Генераторные выражения возвращают итератор, который вычисляет значения по мере необходимости, не требуя материализации всех значений сразу. Это означает, что списковые включения бесполезны при работе с итераторами, возвращающими бесконечный поток или очень большой объём данных. В таких ситуациях предпочтительнее генераторные выражения.216217Генераторные выражения заключаются в круглые скобки («()»), а списковые включения – в квадратные скобки («\[\]»). Генераторные выражения имеют вид:218219```python220( expression for expr in sequence1221 if condition1222 for expr2 in sequence2223 if condition2224 for expr3 in sequence3 ...225 if condition3226 for exprN in sequenceN227 if conditionN )228```229230Опять же, для спискового включения отличаются только внешние скобки (квадратные вместо круглых).231232Элементами генерируемого вывода будут последовательные значения `expression`. Все предложения `if` являются необязательными; если они присутствуют, `expression` вычисляется и добавляется в результат только тогда, когда `condition` истинно.233234Генераторные выражения всегда должны быть записаны внутри круглых скобок, но скобки, обозначающие вызов функции, также подходят. Если нужно создать итератор, который будет немедленно передан функции, можно написать:235236```python237obj_total = sum(obj.count for obj in list_all_objects())238```239240Предложения `for...in` содержат последовательности, по которым производится итерация. Последовательности не обязательно должны быть одинаковой длины, поскольку они перебираются слева направо, **не** параллельно. Для каждого элемента в `sequence1`, `sequence2` перебирается с начала. Затем `sequence3` перебирается для каждой результирующей пары элементов из `sequence1` и `sequence2`.241242Другими словами, списковое включение или генераторное выражение эквивалентно следующему коду Python:243244```python245for expr1 in sequence1:246 if not (condition1):247 continue # Пропустить этот элемент248 for expr2 in sequence2:249 if not (condition2):250 continue # Пропустить этот элемент251 ...252 for exprN in sequenceN:253 if not (conditionN):254 continue # Пропустить этот элемент255256 # Вывести значение257 # выражения.258```259260Это означает, что при наличии нескольких предложений `for...in`, но отсутствии предложений `if`, длина результирующего вывода будет равна произведению длин всех последовательностей. Если есть два списка длины 3, выходной список будет длиной 9 элементов:261262```pycon263>>> seq1 = 'abc'264>>> seq2 = (1,2,3)265>>> [(x,y) for x in seq1 for y in seq2]266[('a', 1), ('a', 2), ('a', 3),267 ('b', 1), ('b', 2), ('b', 3),268 ('c', 1), ('c', 2), ('c', 3)]269```270271Чтобы избежать неоднозначности в грамматике Python, если `expression` создаёт кортеж, его необходимо заключить в круглые скобки. Первое списковое включение ниже является синтаксической ошибкой, а второе – правильным:272273```python274# Синтаксическая ошибка275[ x,y for x in seq1 for y in seq2]276# Верно277[ (x,y) for x in seq1 for y in seq2]278```279280## Генераторы281282Генераторы – это особый класс функций, упрощающих написание итераторов. Обычные функции вычисляют значение и возвращают его, а генераторы возвращают итератор, который возвращает поток значений.283284Вы, несомненно, знакомы с тем, как работают обычные вызовы функций в Python или C. Когда функция вызывается, она получает приватное пространство имён, в котором создаются её локальные переменные. Когда функция достигает оператора `return`, локальные переменные уничтожаются, а значение возвращается вызывающему. Последующий вызов той же функции создаёт новое приватное пространство имён и новый набор локальных переменных. Но что, если бы локальные переменные не отбрасывались при выходе из функции? Что, если бы можно было позже возобновить функцию с того места, где она остановилась? Именно это и обеспечивают генераторы; их можно рассматривать как возобновляемые функции.285286Вот простейший пример функции-генератора:287288```python289def generate_ints(N):290 for i in range(N):291 yield i292```293294Любая функция, содержащая ключевое слово `yield`, является генераторной функцией; это обнаруживается компилятором [байт-кода](https://python-all.ru/2.7/glossary.html#term-bytecode) Python, который в результате компилирует функцию особым образом.295296При вызове генераторной функции она возвращает не одно значение, а объект-генератор, поддерживающий протокол итератора. При выполнении выражения `yield` генератор выводит значение `i`, подобно оператору `return`. Большое различие между `yield` и оператором `return` заключается в том, что при достижении `yield` состояние выполнения генератора приостанавливается, а локальные переменные сохраняются. При следующем вызове метода `.next()` генератора функция возобновит выполнение.297298Вот пример использования генератора `generate_ints()`:299300```python301>>> gen = generate_ints(3)302>>> gen303<generator object generate_ints at ...>304>>> gen.next()3050306>>> gen.next()3071308>>> gen.next()3092310>>> gen.next()311Traceback (most recent call last):312 File "stdin", line 1, in <module>313 File "stdin", line 2, in generate_ints314StopIteration315```316317Можно также написать `for i in generate_ints(5)` или `a,b,c = generate_ints(3)`.318319Внутри генераторной функции оператор `return` может использоваться только без значения и сигнализирует об окончании последовательности значений; после выполнения `return` генератор не может вернуть никаких дальнейших значений. `return` со значением, например `return 5`, внутри генераторной функции является синтаксической ошибкой. Окончание результатов генератора также можно обозначить, вручную возбудив `StopIteration`, или просто позволив потоку выполнения дойти до конца функции.320321Эффекта генераторов можно добиться вручную, написав собственный класс и сохраняя все локальные переменные генератора как переменные экземпляра. Например, возврат списка целых чисел можно реализовать, установив `self.count` в 0, и заставив метод `next()` увеличивать `self.count` и возвращать его. Однако для сколько-нибудь сложного генератора написание соответствующего класса может быть гораздо более запутанным.322323Набор тестов, входящий в библиотеку Python, `test_generators.py`, содержит ряд более интересных примеров. Вот один генератор, который рекурсивно реализует симметричный обход дерева с помощью генераторов.324325```python326# Рекурсивный генератор, порождающий листья дерева в порядке in-order.327def inorder(t):328 if t:329 for x in inorder(t.left):330 yield x331332 yield t.label333334 for x in inorder(t.right):335 yield x336```337338Два других примера в `test_generators.py` дают решения задачи о N ферзях (размещение N ферзей на шахматной доске NxN так, чтобы ни один ферзь не угрожал другому) и о ходе коня (поиск маршрута, который проводит коня через каждую клетку доски NxN, не посещая ни одну клетку дважды).339340### Передача значений в генератор341342В Python 2.4 и ранее генераторы только выдавали результат. Как только код генератора вызывался для создания итератора, не было способа передать новую информацию в функцию при возобновлении её выполнения. Можно было сымитировать эту возможность, заставив генератор обращаться к глобальной переменной или передавая изменяемый объект, который затем изменяли вызывающие стороны, но эти подходы были громоздкими.343344В Python 2.5 появился простой способ передачи значений в генератор. [`yield`](https://python-all.ru/2.7/reference/simple_stmts.html#yield) стал выражением, возвращающим значение, которое можно присвоить переменной или иным образом обработать:345346```python347val = (yield i)348```349350Рекомендуется **всегда** заключать выражение `yield` в скобки, когда вы что-то делаете с возвращаемым значением, как в примере выше. Скобки не всегда обязательны, но проще всегда их добавлять, чем запоминать, когда они нужны.351352(PEP 342 объясняет точные правила: выражение `yield` всегда должно быть заключено в скобки, за исключением случая, когда оно находится на верхнем уровне выражения в правой части присваивания. Это означает, что можно написать `val = yield i`, но необходимо использовать скобки при наличии операции, как в `val = (yield i) + 12`.)353354Значения отправляются в генератор вызовом его метода `send(value)`. Этот метод возобновляет код генератора, и выражение `yield` возвращает указанное значение. Если вызывается обычный метод `next()`, то `yield` возвращает `None`.355356Вот простой счётчик, который увеличивается на 1 и позволяет изменять значение внутреннего счётчика.357358```python359def counter (maximum):360 i = 0361 while i < maximum:362 val = (yield i)363 # Если передано значение, изменить counter.364 if val is not None:365 i = val366 else:367 i += 1368```369370А вот пример изменения счётчика:371372```python373>>> it = counter(10)374>>> print it.next()3750376>>> print it.next()3771378>>> print it.send(8)3798380>>> print it.next()3819382>>> print it.next()383Traceback (most recent call last):384 File "t.py", line 15, in <module>385 print it.next()386StopIteration387```388389Поскольку `yield` часто будет возвращать `None`, всегда следует проверять этот случай. Не следует использовать его значение в выражениях без уверенности, что метод `send()` будет единственным методом, используемым для возобновления генераторной функции.390391В дополнение к `send()`, у генераторов есть два других новых метода:392393- `throw(type, value=None, traceback=None)` используется для возбуждения исключения внутри генератора; исключение возбуждается выражением `yield` в точке, где выполнение генератора приостановлено.394- `close()` возбуждает исключение [`GeneratorExit`](https://python-all.ru/2.7/library/exceptions.html#exceptions.GeneratorExit) внутри генератора для завершения итерации. При получении этого исключения код генератора должен либо возбудить [`GeneratorExit`](https://python-all.ru/2.7/library/exceptions.html#exceptions.GeneratorExit), либо [`StopIteration`](https://python-all.ru/2.7/library/exceptions.html#exceptions.StopIteration); перехватывать исключение и делать что-либо ещё незаконно и вызовет [`RuntimeError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.RuntimeError). `close()` также будет вызван сборщиком мусора Python, когда генератор будет уничтожен сборкой мусора.395396 Если нужно выполнить код очистки при возникновении [`GeneratorExit`](https://python-all.ru/2.7/library/exceptions.html#exceptions.GeneratorExit), рекомендуется использовать конструкцию `try: ... finally:` вместо перехвата [`GeneratorExit`](https://python-all.ru/2.7/library/exceptions.html#exceptions.GeneratorExit).397398Совокупный эффект этих изменений – превращение генераторов из однонаправленных производителей информации одновременно и в производителей, и в потребителей.399400Генераторы также становятся **корутинами** – более обобщённой формой подпрограмм. Подпрограммы имеют одну точку входа и одну точку выхода (начало функции и оператор `return`), тогда как корутины могут иметь много разных точек входа, выхода и возобновления (операторы `yield`).401402## Встроенные функции403404Рассмотрим подробнее встроенные функции, часто используемые с итераторами.405406Две встроенные функции Python, [`map()`](https://python-all.ru/2.7/library/functions.html#map) и [`filter()`](https://python-all.ru/2.7/library/functions.html#filter), несколько устарели; они дублируют возможности списковых включений, но возвращают обычные списки вместо итераторов.407408`map(f, iterA, iterB, ...)` возвращает список, содержащий `f(iterA[0], iterB[0]), f(iterA[1], iterB[1]), f(iterA[2], iterB[2]), ...`.409410```python411>>> def upper(s):412... return s.upper()413```414415```python416>>> map(upper, ['sentence', 'fragment'])417['SENTENCE', 'FRAGMENT']418```419420```python421>>> [upper(s) for s in ['sentence', 'fragment']]422['SENTENCE', 'FRAGMENT']423```424425Как показано выше, того же эффекта можно достичь с помощью спискового включения. Функция [`itertools.imap()`](https://python-all.ru/2.7/library/itertools.html#itertools.imap) делает то же самое, но может работать с бесконечными итераторами; она будет рассмотрена позднее, в разделе о модуле [`itertools`](https://python-all.ru/2.7/library/itertools.html#module-itertools).426427`filter(predicate, iter)` возвращает список, содержащий все элементы последовательности, удовлетворяющие определённому условию, и аналогично дублируется списковыми включениями. **Предикат** – это функция, возвращающая истинностное значение некоторого условия; для использования с [`filter()`](https://python-all.ru/2.7/library/functions.html#filter) предикат должен принимать одно значение.428429```python430>>> def is_even(x):431... return (x % 2) == 0432```433434```python435>>> filter(is_even, range(10))436[0, 2, 4, 6, 8]437```438439Это также можно записать в виде спискового включения:440441```python442>>> [x for x in range(10) if is_even(x)]443[0, 2, 4, 6, 8]444```445446У [`filter()`](https://python-all.ru/2.7/library/functions.html#filter) также есть аналог в модуле [`itertools`](https://python-all.ru/2.7/library/itertools.html#module-itertools) – [`itertools.ifilter()`](https://python-all.ru/2.7/library/itertools.html#itertools.ifilter), который возвращает итератор и поэтому может работать с бесконечными последовательностями, как и [`itertools.imap()`](https://python-all.ru/2.7/library/itertools.html#itertools.imap).447448У `reduce(func, iter, [initial_value])` нет аналога в модуле [`itertools`](https://python-all.ru/2.7/library/itertools.html#module-itertools), поскольку он кумулятивно выполняет операцию над всеми элементами итерируемого объекта и поэтому не может применяться к бесконечным итераторам. `func` должна быть функцией, принимающей два элемента и возвращающей одно значение. [`reduce()`](https://python-all.ru/2.7/library/functions.html#reduce) берёт первые два элемента A и B, возвращённые итератором, и вычисляет `func(A, B)`. Затем он запрашивает третий элемент C, вычисляет `func(func(A, B), C)`, объединяет этот результат с четвёртым возвращённым элементом и продолжает, пока итерируемый объект не исчерпается. Если итерируемый объект не возвращает никаких значений, возбуждается исключение [`TypeError`](https://python-all.ru/2.7/library/exceptions.html#exceptions.TypeError). Если указано начальное значение, оно используется как отправная точка, и `func(initial_value, A)` становится первым вычислением.449450```python451>>> import operator452>>> reduce(operator.concat, ['A', 'BB', 'C'])453'ABBC'454>>> reduce(operator.concat, [])455Traceback (most recent call last):456 ...457TypeError: reduce() of empty sequence with no initial value458>>> reduce(operator.mul, [1,2,3], 1)4596460>>> reduce(operator.mul, [], 1)4611462```463464Если использовать [`operator.add()`](https://python-all.ru/2.7/library/operator.html#operator.add) с [`reduce()`](https://python-all.ru/2.7/library/functions.html#reduce), будут просуммированы все элементы итерируемого объекта. Этот случай настолько распространён, что для него существует специальная встроенная функция [`sum()`](https://python-all.ru/2.7/library/functions.html#sum):465466```python467>>> reduce(operator.add, [1,2,3,4], 0)46810469>>> sum([1,2,3,4])47010471>>> sum([])4720473```474475Однако для многих случаев использования [`reduce()`](https://python-all.ru/2.7/library/functions.html#reduce) может быть понятнее просто написать очевидный цикл [`for`](https://python-all.ru/2.7/reference/compound_stmts.html#for):476477```python478# Вместо:479product = reduce(operator.mul, [1,2,3], 1)480481# Можно написать:482product = 1483for i in [1,2,3]:484 product *= i485```486487`enumerate(iter)` нумерует элементы в итерируемом объекте, возвращая кортежи из двух элементов, содержащие номер и каждый элемент.488489```python490>>> for item in enumerate(['subject', 'verb', 'object']):491... print item492(0, 'subject')493(1, 'verb')494(2, 'object')495```496497[`enumerate()`](https://python-all.ru/2.7/library/functions.html#enumerate) часто используется при переборе списка и записи индексов, для которых выполняются определённые условия:498499```python500f = open('data.txt', 'r')501for i, line in enumerate(f):502 if line.strip() == '':503 print 'Blank line at line #%i' % i504```505506`sorted(iterable, [cmp=None], [key=None], [reverse=False])` собирает все элементы итерируемого объекта в список, сортирует его и возвращает отсортированный результат. Аргументы `cmp`, `key` и `reverse` передаются методу `.sort()` созданного списка.507508```python509>>> import random510>>> # Сгенерировать 8 случайных чисел в диапазоне [0, 10000).511>>> rand_list = random.sample(range(10000), 8)512>>> rand_list513[769, 7953, 9828, 6431, 8442, 9878, 6213, 2207]514>>> sorted(rand_list)515[769, 2207, 6213, 6431, 7953, 8442, 9828, 9878]516>>> sorted(rand_list, reverse=True)517[9878, 9828, 8442, 7953, 6431, 6213, 2207, 769]518```519520(Более подробное обсуждение сортировки см. в мини-руководстве по сортировке в вики Python по адресу [https://wiki.python.org/moin/HowTo/Sorting](https://python-all.ru/2.7/howto/functional.html).)521522Встроенные функции `any(iter)` и `all(iter)` проверяют истинностные значения содержимого итерируемого объекта. [`any()`](https://python-all.ru/2.7/library/functions.html#any) возвращает `True`, если хотя бы один элемент в итерируемом объекте является истинным, а [`all()`](https://python-all.ru/2.7/library/functions.html#all) возвращает `True`, если все элементы являются истинными:523524```python525>>> any([0,1,0])526True527>>> any([0,0,0])528False529>>> any([1,1,1])530True531>>> all([0,1,0])532False533>>> all([0,0,0])534False535>>> all([1,1,1])536True537```538539## Небольшие функции и лямбда-выражение540541При написании программ в функциональном стиле часто требуются небольшие функции, которые выступают в роли предикатов или каким-либо образом комбинируют элементы.542543Если есть подходящая встроенная функция Python или функция из модуля, не нужно определять новую функцию:544545```python546stripped_lines = [line.strip() for line in lines]547existing_files = filter(os.path.exists, file_list)548```549550Если нужной функции не существует, её необходимо написать. Один из способов написания небольших функций – использование оператора `lambda`. `lambda` принимает ряд параметров и выражение, комбинирующее эти параметры, и создаёт небольшую функцию, возвращающую значение выражения:551552```python553lowercase = lambda x: x.lower()554555print_assign = lambda name, value: name + '=' + str(value)556557adder = lambda x, y: x+y558```559560Альтернатива – просто использовать инструкцию `def` и определить функцию обычным способом:561562```python563def lowercase(x):564 return x.lower()565566def print_assign(name, value):567 return name + '=' + str(value)568569def adder(x,y):570 return x + y571```572573Какая альтернатива предпочтительнее? Это вопрос стиля; я обычно стараюсь не использовать `lambda`.574575Одна из причин моего предпочтения в том, что `lambda` весьма ограничен в определении функций. Результат должен быть вычислим как одно выражение, а это значит, что нельзя использовать множественные сравнения `if... elif... else` или инструкции `try... except`. Если попытаться сделать слишком много в инструкции `lambda`, получится чрезмерно сложное выражение, которое трудно читать. Быстро, что делает следующий код?576577```python578total = reduce(lambda a, b: (0, a[1] + b[1]), items)[1]579```580581Можно разобраться, но требуется время, чтобы распутать выражение и понять, что происходит. Использование коротких вложенных инструкций `def` немного улучшает ситуацию:582583```python584def combine (a, b):585 return 0, a[1] + b[1]586587total = reduce(combine, items)[1]588```589590Но лучше всего было бы просто использовать цикл `for`:591592```python593total = 0594for a, b in items:595 total += b596```597598Или встроенную функцию [`sum()`](https://python-all.ru/2.7/library/functions.html#sum) и выражение-генератор:599600```python601total = sum(b for a,b in items)602```603604Многие применения [`reduce()`](https://python-all.ru/2.7/library/functions.html#reduce) становятся понятнее, если записать их в виде циклов `for`.605606Фредрик Лунд однажды предложил следующий набор правил для рефакторинга использования `lambda`:6076081. Написать лямбда-функцию.6092. Написать комментарий, объясняющий, что, черт возьми, делает эта лямбда.6103. Некоторое время изучать комментарий и придумать имя, отражающее суть комментария.6114. Преобразовать лямбду в инструкцию def, используя это имя.6125. Удалить комментарий.613614Мне очень нравятся эти правила, но вы вольны не соглашаться с тем, что этот стиль без лямбда лучше.615616## Модуль itertools617618Модуль [`itertools`](https://python-all.ru/2.7/library/itertools.html#module-itertools) содержит ряд часто используемых итераторов, а также функции для комбинирования нескольких итераторов. В данном разделе будут представлены возможности модуля на небольших примерах.619620Функции модуля делятся на несколько широких категорий:621622- Функции, создающие новый итератор на основе существующего.623- Функции для передачи элементов итератора в качестве аргументов функции.624- Функции для выбора части вывода итератора.625- Функция для группировки вывода итератора.626627### Создание новых итераторов628629`itertools.count(n)` возвращает бесконечный поток целых чисел, каждый раз увеличивающихся на 1. Можно дополнительно указать начальное число; по умолчанию оно равно 0:630631```python632itertools.count() =>633 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...634itertools.count(10) =>635 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...636```637638`itertools.cycle(iter)` сохраняет копию содержимого переданного итерируемого объекта и возвращает новый итератор, который возвращает его элементы от первого до последнего. Этот новый итератор будет повторять эти элементы бесконечно.639640```python641itertools.cycle([1,2,3,4,5]) =>642 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...643```644645`itertools.repeat(elem, [n])` возвращает переданный элемент `n` раз или возвращает его бесконечно, если `n` не указан.646647```python648itertools.repeat('abc') =>649 abc, abc, abc, abc, abc, abc, abc, abc, abc, abc, ...650itertools.repeat('abc', 5) =>651 abc, abc, abc, abc, abc652```653654`itertools.chain(iterA, iterB, ...)` принимает произвольное количество итерируемых объектов на вход и возвращает все элементы первого итератора, затем все элементы второго и так далее, пока все итерируемые объекты не будут исчерпаны.655656```python657itertools.chain(['a', 'b', 'c'], (1, 2, 3)) =>658 a, b, c, 1, 2, 3659```660661`itertools.izip(iterA, iterB, ...)` берёт по одному элементу из каждого итерируемого объекта и возвращает их в виде кортежа:662663```python664itertools.izip(['a', 'b', 'c'], (1, 2, 3)) =>665 ('a', 1), ('b', 2), ('c', 3)666```667668Эта функция похожа на встроенную [`zip()`](https://python-all.ru/2.7/library/functions.html#zip), но не создаёт список в памяти и не исчерпывает все входные итераторы перед возвратом; вместо этого кортежи создаются и возвращаются только по запросу. (Технический термин для такого поведения – [ленивые вычисления](https://python-all.ru/2.7/howto/functional.html).)669670Этот итератор предназначен для использования с итерируемыми объектами одинаковой длины. Если длины различаются, результирующий поток данных будет иметь длину кратчайшего итерируемого объекта.671672```python673itertools.izip(['a', 'b'], (1, 2, 3)) =>674 ('a', 1), ('b', 2)675```676677Тем не менее, так делать не стоит, поскольку из более длинных итераторов может быть взят и отброшен элемент. Это означает, что использовать эти итераторы дальше нельзя – есть риск пропустить отброшенный элемент.678679`itertools.islice(iter, [start], stop, [step])` возвращает поток данных, представляющий собой срез итератора. С одним аргументом `stop` она вернёт первые `stop` элементов. Если указать начальный индекс, будут возвращены `stop-start` элементов, а если задать значение для `step`, элементы будут пропускаться соответствующим образом. В отличие от срезов строк и списков Python, для `start`, `stop` и `step` нельзя использовать отрицательные значения.680681```python682itertools.islice(range(10), 8) =>683 0, 1, 2, 3, 4, 5, 6, 7684itertools.islice(range(10), 2, 8) =>685 2, 3, 4, 5, 6, 7686itertools.islice(range(10), 2, 8, 2) =>687 2, 4, 6688```689690`itertools.tee(iter, [n])` реплицирует итератор; возвращает `n` независимых итераторов, каждый из которых будет возвращать содержимое исходного итератора. Если не указать значение `n`, по умолчанию используется 2. Репликация итераторов требует сохранения части содержимого исходного итератора, поэтому при большом размере итератора, когда один из новых итераторов используется больше других, это может потребовать значительного объёма памяти.691692```python693itertools.tee( itertools.count() ) =>694 iterA, iterB695696where iterA ->697 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...698699and iterB ->700 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...701```702703### Применение функций к элементам704705Две функции используются для вызова других функций над содержимым итерируемого объекта.706707`itertools.imap(f, iterA, iterB, ...)` возвращает поток данных, содержащий `f(iterA[0], iterB[0]), f(iterA[1], iterB[1]), f(iterA[2], iterB[2]), ...`:708709```python710itertools.imap(operator.add, [5, 6, 5], [1, 2, 3]) =>711 6, 8, 8712```713714Модуль `operator` содержит набор функций, соответствующих операторам Python. Некоторые примеры: `operator.add(a, b)` (складывает два значения), `operator.ne(a, b)` (то же, что `a!=b`) и `operator.attrgetter('id')` (возвращает вызываемый объект, который получает атрибут `"id"`).715716`itertools.starmap(func, iter)` предполагает, что итерируемый объект возвращает поток кортежей, и вызывает `f()`, используя эти кортежи как аргументы:717718```python719itertools.starmap(os.path.join,720 [('/usr', 'bin', 'java'), ('/bin', 'python'),721 ('/usr', 'bin', 'perl'),('/usr', 'bin', 'ruby')])722=>723 /usr/bin/java, /bin/python, /usr/bin/perl, /usr/bin/ruby724```725726### Выбор элементов727728Ещё одна группа функций выбирает подмножество элементов итератора на основе предиката.729730`itertools.ifilter(predicate, iter)` возвращает все элементы, для которых предикат возвращает истину:731732```python733def is_even(x):734 return (x % 2) == 0735736itertools.ifilter(is_even, itertools.count()) =>737 0, 2, 4, 6, 8, 10, 12, 14, ...738```739740`itertools.ifilterfalse(predicate, iter)` – противоположная функция, возвращает все элементы, для которых предикат возвращает ложь:741742```python743itertools.ifilterfalse(is_even, itertools.count()) =>744 1, 3, 5, 7, 9, 11, 13, 15, ...745```746747`itertools.takewhile(predicate, iter)` возвращает элементы, пока предикат возвращает истину. Как только предикат возвращает ложь, итератор сигнализирует о завершении результатов.748749```python750def less_than_10(x):751 return (x < 10)752753itertools.takewhile(less_than_10, itertools.count()) =>754 0, 1, 2, 3, 4, 5, 6, 7, 8, 9755756itertools.takewhile(is_even, itertools.count()) =>757 0758```759760`itertools.dropwhile(predicate, iter)` отбрасывает элементы, пока предикат возвращает истину, а затем возвращает оставшиеся результаты итерируемого объекта.761762```python763itertools.dropwhile(less_than_10, itertools.count()) =>764 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...765766itertools.dropwhile(is_even, itertools.count()) =>767 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...768```769770### Группировка элементов771772Последняя функция, которую я рассмотрю, `itertools.groupby(iter, key_func=None)`, является самой сложной. `key_func(elem)` – это функция, которая может вычислять ключевое значение для каждого элемента, возвращаемого итератором. Если не указать ключевую функцию, ключом является просто сам элемент.773774`groupby()` собирает все последовательные элементы из базового итерируемого объекта с одинаковым ключевым значением и возвращает поток кортежей из двух элементов, содержащих ключевое значение и итератор для элементов с этим ключом.775776```python777city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'),778 ('Anchorage', 'AK'), ('Nome', 'AK'),779 ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ'),780 ...781 ]782783def get_state ((city, state)):784 return state785786itertools.groupby(city_list, get_state) =>787 ('AL', iterator-1),788 ('AK', iterator-2),789 ('AZ', iterator-3), ...790791where792iterator-1 =>793 ('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL')794iterator-2 =>795 ('Anchorage', 'AK'), ('Nome', 'AK')796iterator-3 =>797 ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')798```799800`groupby()` предполагает, что содержимое базового итерируемого объекта уже отсортировано по ключу. Обратите внимание, что возвращаемые итераторы также используют базовый итерируемый объект, поэтому необходимо потребить результаты итератора-1 перед запросом итератора-2 и соответствующего ему ключа.801802## Модуль functools803804Модуль [`functools`](https://python-all.ru/2.7/library/functools.html#module-functools) в Python 2.5 содержит несколько функций высшего порядка. **Функция высшего порядка** принимает одну или несколько функций на вход и возвращает новую функцию. Самым полезным инструментом в этом модуле является функция [`functools.partial()`](https://python-all.ru/2.7/library/functools.html#functools.partial).805806Для программ в функциональном стиле иногда требуется создавать варианты существующих функций с частично заполненными параметрами. Рассмотрим функцию Python `f(a, b, c)`; возможно, вы захотите создать новую функцию `g(b, c)`, эквивалентную `f(1, b, c)`; вы заполняете значение для одного из параметров `f()`. Это называется «частичным применением функции».807808Конструктор `partial` принимает аргументы `(function, arg1, arg2, ... kwarg1=value1, kwarg2=value2)`. Полученный объект является вызываемым, поэтому его можно просто вызвать, чтобы выполнить `function` с заданными аргументами.809810Вот небольшой, но реалистичный пример:811812```python813import functools814815def log (message, subsystem):816 "Write the contents of 'message' to the specified subsystem."817 print '%s: %s' % (subsystem, message)818 ...819820server_log = functools.partial(log, subsystem='server')821server_log('Unable to open socket')822```823824### Модуль operator825826Модуль [`operator`](https://python-all.ru/2.7/library/operator.html#module-operator) упоминался ранее. Он содержит набор функций, соответствующих операторам Python. Эти функции часто полезны в функциональном коде, поскольку избавляют от написания тривиальных функций, выполняющих одну операцию.827828Некоторые функции этого модуля:829830- Математические операции: `add()`, `sub()`, `mul()`, `div()`, `floordiv()`, `abs()`, …831- Логические операции: `not_()`, `truth()`.832- Побитовые операции: `and_()`, `or_()`, `invert()`.833- Сравнения: `eq()`, `ne()`, `lt()`, `le()`, `gt()` и `ge()`.834- Идентичность объектов: `is_()`, `is_not()`.835836Полный список – в документации модуля operator.837838## История изменений и благодарности839840Автор хотел бы поблагодарить следующих людей за предложения, исправления и помощь с различными черновиками этой статьи: Ian Bicking, Nick Coghlan, Nick Efford, Raymond Hettinger, Jim Jewett, Mike Krell, Leandro Lameiro, Jussi Salmela, Collin Winter, Blake Winton.841842Версия 0.1: опубликована 30 июня 2006 г.843844Версия 0.11: опубликована 1 июля 2006 г. Исправлены опечатки.845846Версия 0.2: опубликована 10 июля 2006 г. Разделы о выражениях-генераторах и списковых включениях объединены в один. Исправлены опечатки.847848Версия 0.21: добавлены дополнительные ссылки, предложенные в списке рассылки tutor.849850Версия 0.30: добавлен раздел о модуле `functional`, написанный Коллином Уинтером; добавлен короткий раздел о модуле operator; несколько других правок.851852## Ссылки853854### Общие855856**Структура и интерпретация компьютерных программ**, автор Harold Abelson и Gerald Jay Sussman при участии Julie Sussman. Полный текст доступен по адресу [https://mitpress.mit.edu/sicp/](https://python-all.ru/2.7/howto/functional.html). В этом классическом учебнике по информатике, главы 2 и 3 рассматривают использование последовательностей и потоков для организации потока данных внутри программы. Книга использует Scheme для своих примеров, но многие из подходов к проектированию, описанных в этих главах, применимы к функциональному стилю кода Python.857858[http://www.defmacro.org/ramblings/fp.html](https://python-all.ru/2.7/howto/functional.html): Общее введение в функциональное программирование на примерах Java, содержащее обширное историческое введение.859860[https://en.wikipedia.org/wiki/Functional\_programming](https://python-all.ru/2.7/howto/functional.html): общая статья в Википедии, описывающая функциональное программирование.861862[https://en.wikipedia.org/wiki/Coroutine](https://python-all.ru/2.7/howto/functional.html): статья о корутинах.863864[https://en.wikipedia.org/wiki/Currying](https://python-all.ru/2.7/howto/functional.html): статья о концепции каррирования.865866### Специфическое для Python867868[http://gnosis.cx/TPiP/](https://python-all.ru/2.7/howto/functional.html): Первая глава книги Дэвида Мертца *Text Processing in Python* рассматривает функциональное программирование для обработки текста в разделе «Использование функций высшего порядка в обработке текста».869870Мерц также написал серию статей из трёх частей по функциональному программированию для сайта IBM DeveloperWorks; см.871872[часть 1](https://python-all.ru/2.7/howto/functional.html), [часть 2](https://python-all.ru/2.7/howto/functional.html) и [часть 3](https://python-all.ru/2.7/howto/functional.html),873874### Документация Python875876Документация по модулю [`itertools`](https://python-all.ru/2.7/library/itertools.html#module-itertools).877878Документация по модулю [`operator`](https://python-all.ru/2.7/library/operator.html#module-operator).879880[**PEP 289**](https://python-all.ru/2.7/howto/functional.html): “Генераторные выражения”881882[**PEP 342**](https://python-all.ru/2.7/howto/functional.html): “Корутины через улучшенные генераторы” описывает новые возможности генераторов в Python 2.5.883