Документация Python неофициальный перевод

functional.md

936 строк · 79.8 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.15/howto/functional.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Руководство по функциональному программированию89В этом документе мы рассмотрим возможности Python, подходящие для реализации программ в функциональном стиле. После введения в концепции функционального программирования мы рассмотрим такие языковые средства, как [итератор](https://python-all.ru/3.15/glossary.html#term-iterator)ы и [генератор](https://python-all.ru/3.15/glossary.html#term-generator)ы, а также соответствующие библиотечные модули, такие как [`itertools`](https://python-all.ru/3.15/library/itertools.html#module-itertools) и [`functools`](https://python-all.ru/3.15/library/functools.html#module-functools).1011## Введение1213В этом разделе объясняется основная концепция функционального программирования; если вас интересуют только особенности языка Python, переходите к следующему разделу [Итераторы](https://python-all.ru/3.15/howto/functional.html#functional-howto-iterators).1415Языки программирования поддерживают декомпозицию задач несколькими различными способами:1617- Большинство языков программирования являются **процедурными**: программы представляют собой списки инструкций, которые указывают компьютеру, что делать с входными данными программы. C, Pascal и даже оболочки Unix являются процедурными языками.18- В **декларативных** языках пишется спецификация, описывающая решаемую задачу, а реализация языка самостоятельно определяет, как эффективно выполнить вычисления. SQL – это декларативный язык, с которым вы, скорее всего, знакомы; SQL-запрос описывает набор данных, который вы хотите получить, а механизм SQL решает, сканировать таблицы или использовать индексы, какие подзапросы выполнять в первую очередь и т.д.19- **Объектно-ориентированные** программы манипулируют коллекциями объектов. Объекты имеют внутреннее состояние и поддерживают методы, которые запрашивают или изменяют это внутреннее состояние определённым образом. Smalltalk и Java являются объектно-ориентированными языками. C++ и Python – языки, поддерживающие объектно-ориентированное программирование, но не навязывающие использование объектно-ориентированных возможностей.20- **Функциональное** программирование разбивает задачу на набор функций. В идеале функции только принимают входные данные и выдают результаты, не имея внутреннего состояния, влияющего на вывод для заданного входа. К известным функциональным языкам относятся семейство ML (Standard ML, OCaml и другие варианты) и Haskell.2122Разработчики некоторых языков программирования решают сделать упор на один конкретный подход к программированию. Это часто затрудняет написание программ, использующих другой подход. Другие языки являются мультипарадигмальными и поддерживают несколько различных подходов. Lisp, C++ и Python – мультипарадигмальные; в каждом из этих языков можно писать программы или библиотеки, преимущественно процедурные, объектно-ориентированные или функциональные. В большой программе разные разделы могут быть написаны с использованием разных подходов; например, графический интерфейс может быть объектно-ориентированным, а логика обработки – процедурной или функциональной.2324В функциональной программе входные данные проходят через набор функций. Каждая функция обрабатывает свой вход и выдаёт некоторый результат. Функциональный стиль не одобряет функции с побочными эффектами, изменяющими внутреннее состояние или вносящими другие изменения, невидимые в возвращаемом значении функции. Функции, не имеющие побочных эффектов вообще, называются **чисто функциональными**. Избегание побочных эффектов означает отказ от использования структур данных, которые обновляются в процессе выполнения программы; результат каждой функции должен зависеть только от её входных данных.2526Некоторые языки очень строги в отношении чистоты и даже не имеют операторов присваивания, таких как `a=3` или `c = a + b`, но трудно избежать всех побочных эффектов, таких как вывод на экран или запись в файл на диске. Другим примером является вызов функции [`print()`](https://python-all.ru/3.15/library/functions.html#print) или [`time.sleep()`](https://python-all.ru/3.15/library/time.html#time.sleep), ни одна из которых не возвращает полезного значения. Обе вызываются только ради побочных эффектов – отправки текста на экран или приостановки выполнения на секунду.2728Программы на Python, написанные в функциональном стиле, обычно не доходят до крайности полного отказа от ввода-вывода или присваиваний; вместо этого они предоставляют интерфейс, выглядящий функциональным, но внутри используют не функциональные возможности. Например, реализация функции всё равно будет использовать присваивания локальным переменным, но не будет изменять глобальные переменные или иметь другие побочные эффекты.2930Функциональное программирование можно считать противоположностью объектно-ориентированного. Объекты – это небольшие капсулы, содержащие некоторое внутреннее состояние вместе с набором вызовов методов, позволяющих изменять это состояние, а программы состоят из выполнения правильного набора изменений состояния. Функциональное программирование стремится максимально избегать изменений состояния и работает с данными, передаваемыми между функциями. В Python вы можете комбинировать эти два подхода, написав функции, которые принимают и возвращают экземпляры, представляющие объекты в вашем приложении (сообщения электронной почты, транзакции и т.д.).3132Функциональный дизайн может показаться странным ограничением для работы. Зачем избегать объектов и побочных эффектов? У функционального стиля есть теоретические и практические преимущества:3334- Формальная доказуемость.35- Модульность.36- Композируемость.37- Лёгкость отладки и тестирования.3839### Формальная доказуемость4041Теоретическое преимущество заключается в том, что легче построить математическое доказательство корректности функциональной программы.4243Уже долгое время исследователи заинтересованы в поиске способов математического доказательства корректности программ. Это отличается от тестирования программы на множестве входных данных и заключения, что её вывод обычно корректен, или чтения исходного кода программы и заключения, что код выглядит правильно; цель – строгое доказательство того, что программа выдаёт правильный результат для всех возможных входных данных.4445Метод, используемый для доказательства корректности программ, заключается в записи **инвариантов** – свойств входных данных и переменных программы, которые всегда истинны. Для каждой строки кода затем показывается, что если инварианты X и Y истинны **до** выполнения строки, то слегка изменённые инварианты X’ и Y’ истинны **после** выполнения строки. Это продолжается до конца программы, после чего инварианты должны соответствовать желаемым условиям на выход программы.4647Избегание присваиваний в функциональном программировании возникло потому, что присваивания сложно обрабатывать с помощью этого метода; присваивания могут нарушить инварианты, которые были истинны до присваивания, не порождая новых инвариантов, которые можно было бы распространить дальше.4849К сожалению, доказательство корректности программ в значительной степени непрактично и не применимо к программному обеспечению на Python. Даже тривиальные программы требуют доказательств длиной в несколько страниц; доказательство корректности для умеренно сложной программы было бы огромным, и мало какие (или вообще никакие) из программ, которые вы используете ежедневно (интерпретатор Python, ваш XML-парсер, веб-браузер), могли бы быть доказаны корректными. Даже если бы вы записали или сгенерировали доказательство, возник бы вопрос его проверки; возможно, в нём есть ошибка, и вы ошибочно полагаете, что доказали корректность программы.5051### Модульность5253Более практическое преимущество функционального программирования заключается в том, что оно заставляет разбивать задачу на небольшие части. Программы становятся более модульными. Проще определить и написать маленькую функцию, которая делает одно дело, чем большую функцию, выполняющую сложное преобразование. Маленькие функции также легче читать и проверять на ошибки.5455### Лёгкость отладки и тестирования5657Тестировать и отлаживать программу в функциональном стиле проще.5859Отладка упрощается, потому что функции обычно малы и чётко определены. Когда программа не работает, каждая функция является точкой интерфейса, где можно проверить корректность данных. Можно просмотреть промежуточные входные и выходные данные, чтобы быстро изолировать функцию, ответственную за ошибку.6061Тестирование проще, потому что каждая функция является потенциальным объектом для модульного теста. Функции не зависят от состояния системы, которое нужно воспроизводить перед запуском теста; вместо этого нужно только синтезировать правильный вход и затем проверить, что выход соответствует ожиданиям.6263### Композируемость6465В процессе работы над программой в функциональном стиле вы напишете ряд функций с различными входными и выходными данными. Некоторые из этих функций будут неизбежно специализированы для конкретного приложения, но другие будут полезны в самых разных программах. Например, функция, принимающая путь к каталогу и возвращающая все XML-файлы в нём, или функция, принимающая имя файла и возвращающая его содержимое, могут применяться во многих различных ситуациях.6667Со временем у вас сформируется личная библиотека утилит. Часто вы будете собирать новые программы, компонуя существующие функции в новой конфигурации и написав несколько функций, специализированных для текущей задачи.6869## Итераторы7071Я начну с рассмотрения возможности языка Python, которая является важной основой для написания функциональных программ: итераторов.7273Итератор – это объект, представляющий поток данных; этот объект возвращает данные по одному элементу за раз. Итератор Python должен поддерживать метод [`__next__()`](https://python-all.ru/3.15/library/stdtypes.html#iterator.__next__), который не принимает аргументов и всегда возвращает следующий элемент потока. Если в потоке больше нет элементов, `__next__()` должно возбуждать исключение [`StopIteration`](https://python-all.ru/3.15/library/exceptions.html#StopIteration). Однако итераторы не обязаны быть конечными; вполне допустимо написать итератор, который порождает бесконечный поток данных.7475Встроенная функция [`iter()`](https://python-all.ru/3.15/library/functions.html#iter) принимает произвольный объект и пытается вернуть итератор, который будет возвращать содержимое или элементы объекта, возбуждая [`TypeError`](https://python-all.ru/3.15/library/exceptions.html#TypeError), если объект не поддерживает итерацию. Несколько встроенных типов данных Python поддерживают итерацию, наиболее распространённые – списки и словари. Объект называется [итерируемым](https://python-all.ru/3.15/glossary.html#term-iterable), если для него можно получить итератор.7677Можно поэкспериментировать с интерфейсом итерации вручную:7879```python80>>> L = [1, 2, 3]81>>> it = iter(L)82>>> it83<...iterator object at ...>84>>> it.__next__()  # то же, что и next(it)85186>>> next(it)87288>>> next(it)89390>>> next(it)91Traceback (most recent call last):92  File "<stdin>", line 1, in <module>93StopIteration94>>>95```9697Python ожидает итерируемые объекты в нескольких различных контекстах, самым важным из которых является оператор [`for`](https://python-all.ru/3.15/reference/compound_stmts.html#for). В операторе `for X in Y` Y должен быть итератором или объектом, для которого [`iter()`](https://python-all.ru/3.15/library/functions.html#iter) может создать итератор. Эти два оператора эквивалентны:9899```python100for i in iter(obj):101    print(i)102103for i in obj:104    print(i)105```106107Итераторы можно материализовать в списки или кортежи с помощью функций-конструкторов [`list()`](https://python-all.ru/3.15/library/stdtypes.html#list) или [`tuple()`](https://python-all.ru/3.15/library/stdtypes.html#tuple):108109```python110>>> L = [1, 2, 3]111>>> iterator = iter(L)112>>> t = tuple(iterator)113>>> t114(1, 2, 3)115```116117Распаковка последовательностей также поддерживает итераторы: если известно, что итератор вернёт N элементов, их можно распаковать в N-кортеж:118119```python120>>> L = [1, 2, 3]121>>> iterator = iter(L)122>>> a, b, c = iterator123>>> a, b, c124(1, 2, 3)125```126127Встроенные функции, такие как [`max()`](https://python-all.ru/3.15/library/functions.html#max) и [`min()`](https://python-all.ru/3.15/library/functions.html#min), могут принимать один аргумент-итератор и возвращать наибольший или наименьший элемент. Операторы `"in"` и `"not in"` также поддерживают итераторы: `X in iterator` истинно, если X найден в потоке, возвращаемом итератором. С бесконечным итератором возникнут очевидные проблемы: `max()` и `min()` никогда не завершатся, а если элемент X никогда не появится в потоке, операторы `"in"` и `"not in"` тоже не вернут результат.128129Обратите внимание, что по итератору можно двигаться только вперёд; нет способа получить предыдущий элемент, сбросить итератор или создать его копию. Объекты-итераторы могут опционально предоставлять эти дополнительные возможности, но протокол итератора определяет только метод [`__next__()`](https://python-all.ru/3.15/library/stdtypes.html#iterator.__next__). Поэтому функции могут потребить весь вывод итератора, и если нужно выполнить другое действие с тем же потоком, придётся создавать новый итератор.130131### Типы данных, поддерживающие итераторы132133Мы уже видели, как списки и кортежи поддерживают итераторы. На самом деле любой тип последовательности Python, например строки, автоматически поддерживает создание итератора.134135Вызов [`iter()`](https://python-all.ru/3.15/library/functions.html#iter) для словаря возвращает итератор, который будет перебирать ключи словаря:136137```python138>>> m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,139...      'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}140>>> for key in m:141...     print(key, m[key])142Jan 1143Feb 2144Mar 3145Apr 4146May 5147Jun 6148Jul 7149Aug 8150Sep 9151Oct 10152Nov 11153Dec 12154```155156Обратите внимание, что начиная с Python 3.7 порядок итерации словаря гарантированно совпадает с порядком вставки. В более ранних версиях поведение не было определено и могло различаться в разных реализациях.157158Применение [`iter()`](https://python-all.ru/3.15/library/functions.html#iter) к словарю всегда перебирает ключи, но у словарей есть методы, возвращающие другие итераторы. Если нужно перебрать значения или пары ключ/значение, можно явно вызвать методы [`values()`](https://python-all.ru/3.15/library/stdtypes.html#dict.values) или [`items()`](https://python-all.ru/3.15/library/stdtypes.html#dict.items), чтобы получить соответствующий итератор.159160Конструктор [`dict()`](https://python-all.ru/3.15/library/stdtypes.html#dict) может принимать итератор, возвращающий конечный поток кортежей `(key, value)`:161162```python163>>> L = [('Italy', 'Rome'), ('France', 'Paris'), ('US', 'Washington DC')]164>>> dict(iter(L))165{'Italy': 'Rome', 'France': 'Paris', 'US': 'Washington DC'}166```167168Файлы также поддерживают итерацию путём вызова метода [`readline()`](https://python-all.ru/3.15/library/io.html#io.TextIOBase.readline), пока в файле не закончатся строки. Это означает, что можно читать каждую строку файла так:169170```python171for line in file:172    # сделать что-то для каждой строки173    ...174```175176Множества могут принимать своё содержимое из итерируемого объекта и позволяют перебирать элементы множества:177178```python179>>> S = {2, 3, 5, 7, 11, 13}180>>> for i in S:181...     print(i)18221833184518571861118713188```189190## Генераторные выражения и списковые включения191192Две распространённые операции над выводом итератора: 1) выполнение некоторого действия для каждого элемента, 2) выбор подмножества элементов, удовлетворяющих некоторому условию. Например, имея список строк, можно удалить завершающие пробелы из каждой строки или извлечь все строки, содержащие заданную подстроку.193194Списковые включения и генераторные выражения (сокращённо «listcomps» и «genexps») – это краткая запись для таких операций, заимствованная из функционального языка программирования Haskell ([https://www.haskell.org/](https://python-all.ru/3.15/howto/functional.html)). Удалить все пробелы из потока строк можно следующим кодом:195196```python197>>> line_list = ['  line 1\n', 'line 2  \n', ' \n', '']198199>>> # Генераторное выражение – возвращает итератор200>>> stripped_iter = (line.strip() for line in line_list)201202>>> # Списочное выражение – возвращает список203>>> stripped_list = [line.strip() for line in line_list]204```205206Можно выбрать только определённые элементы, добавив условие `"if"`:207208```python209>>> stripped_list = [line.strip() for line in line_list210...                  if line != ""]211```212213При использовании спискового включения возвращается список Python; `stripped_list` – это список, содержащий результирующие строки, а не итератор. Генераторные выражения возвращают итератор, который вычисляет значения по мере необходимости, не требуя материализации всех значений сразу. Это означает, что списковые включения бесполезны при работе с итераторами, возвращающими бесконечный поток или очень большой объём данных. В таких ситуациях предпочтительнее генераторные выражения.214215Генераторные выражения заключаются в круглые скобки («()»), а списковые включения – в квадратные скобки («\[\]»). Генераторные выражения имеют вид:216217```python218( expression for expr in sequence1219             if condition1220             for expr2 in sequence2221             if condition2222             for expr3 in sequence3223             if condition3224             ...225             for exprN in sequenceN226             if conditionN )227```228229Опять же, для спискового включения отличаются только внешние скобки (квадратные вместо круглых).230231Элементами генерируемого вывода будут последовательные значения `expression`. Все предложения `if` являются необязательными; если они присутствуют, `expression` вычисляется и добавляется в результат только тогда, когда `condition` истинно.232233Генераторные выражения всегда должны быть записаны внутри круглых скобок, но скобки, обозначающие вызов функции, также подходят. Если нужно создать итератор, который будет немедленно передан функции, можно написать:234235```python236obj_total = sum(obj.count for obj in list_all_objects())237```238239Предложения `for...in` содержат последовательности, по которым производится итерация. Последовательности не обязательно должны быть одинаковой длины, поскольку они перебираются слева направо, **не** параллельно. Для каждого элемента в `sequence1`, `sequence2` перебирается с начала. Затем `sequence3` перебирается для каждой результирующей пары элементов из `sequence1` и `sequence2`.240241Другими словами, списковое включение или генераторное выражение эквивалентно следующему коду Python:242243```python244for expr1 in sequence1:245    if not (condition1):246        continue   # Пропустить этот элемент247    for expr2 in sequence2:248        if not (condition2):249            continue   # Пропустить этот элемент250        ...251        for exprN in sequenceN:252            if not (conditionN):253                continue   # Пропустить этот элемент254255            # Вывести значение256            # выражения.257```258259Это означает, что при наличии нескольких предложений `for...in`, но отсутствии предложений `if`, длина результирующего вывода будет равна произведению длин всех последовательностей. Если есть два списка длины 3, выходной список будет длиной 9 элементов:260261```python262>>> seq1 = 'abc'263>>> seq2 = (1, 2, 3)264>>> [(x, y) for x in seq1 for y in seq2]265[('a', 1), ('a', 2), ('a', 3),266 ('b', 1), ('b', 2), ('b', 3),267 ('c', 1), ('c', 2), ('c', 3)]268```269270Чтобы избежать неоднозначности в грамматике Python, если `expression` создаёт кортеж, его необходимо заключить в круглые скобки. Первое списковое включение ниже является синтаксической ошибкой, а второе – правильным:271272```python273# Синтаксическая ошибка274[x, y for x in seq1 for y in seq2]275# Верно276[(x, y) for x in seq1 for y in seq2]277```278279## Генераторы280281Генераторы – это особый класс функций, упрощающих написание итераторов. Обычные функции вычисляют значение и возвращают его, а генераторы возвращают итератор, который возвращает поток значений.282283Вы, несомненно, знакомы с тем, как работают обычные вызовы функций в Python или C. Когда функция вызывается, она получает приватное пространство имён, в котором создаются её локальные переменные. Когда функция достигает оператора `return`, локальные переменные уничтожаются, а значение возвращается вызывающему. Последующий вызов той же функции создаёт новое приватное пространство имён и новый набор локальных переменных. Но что, если бы локальные переменные не отбрасывались при выходе из функции? Что, если бы можно было позже возобновить функцию с того места, где она остановилась? Именно это и обеспечивают генераторы; их можно рассматривать как возобновляемые функции.284285Вот простейший пример функции-генератора:286287```python288>>> def generate_ints(N):289...    for i in range(N):290...        yield i291```292293Любая функция, содержащая ключевое слово [`yield`](https://python-all.ru/3.15/reference/simple_stmts.html#yield), является функцией-генератором; это обнаруживается компилятором [байт-кода](https://python-all.ru/3.15/glossary.html#term-bytecode) Python, который соответствующим образом компилирует функцию.294295При вызове функции-генератора она не возвращает одно значение; вместо этого возвращается объект-генератор, поддерживающий протокол итератора. При выполнении выражения `yield` генератор выводит значение `i`, аналогично оператору `return`. Основное различие между `yield` и оператором `return` заключается в том, что при достижении `yield` состояние выполнения генератора приостанавливается, а локальные переменные сохраняются. При следующем вызове метода [`__next__()`](https://python-all.ru/3.15/reference/expressions.html#generator.__next__) генератора функция возобновляет выполнение.296297Вот пример использования генератора `generate_ints()`:298299```python300>>> gen = generate_ints(3)301>>> gen302<generator object generate_ints at ...>303>>> next(gen)3040305>>> next(gen)3061307>>> next(gen)3082309>>> next(gen)310Traceback (most recent call last):311  File "stdin", line 1, in <module>312  File "stdin", line 2, in generate_ints313StopIteration314```315316Можно также написать `for i in generate_ints(5)` или `a, b, c = generate_ints(3)`.317318Внутри функции-генератора `return value` вызывает возбуждение `StopIteration(value)` из метода [`__next__()`](https://python-all.ru/3.15/reference/expressions.html#generator.__next__). Как только это происходит или достигается конец функции, поток значений завершается, и генератор больше не может выдавать значения.319320Можно добиться эффекта генераторов вручную, написав собственный класс и сохранив все локальные переменные генератора как переменные экземпляра. Например, возврат списка целых чисел можно выполнить, установив `self.count` в 0 и заставив метод [`__next__()`](https://python-all.ru/3.15/library/stdtypes.html#iterator.__next__) увеличивать `self.count` и возвращать его. Однако для сколько-нибудь сложного генератора написание соответствующего класса может быть гораздо более запутанным.321322Тестовый набор, входящий в библиотеку Python, [Lib/test/test\_generators.py](https://python-all.ru/src/3.15/Lib/test/test_generators.py), содержит ряд более интересных примеров. Вот один генератор, который реализует обход дерева в порядке (in-order) с использованием генераторов рекурсивно.323324```python325# Рекурсивный генератор, порождающий листья дерева в порядке in-order.326def inorder(t):327    if t:328        for x in inorder(t.left):329            yield x330331        yield t.label332333        for x in inorder(t.right):334            yield x335```336337Два других примера в `test_generators.py` дают решения задачи о N ферзях (размещение N ферзей на шахматной доске NxN так, чтобы ни один ферзь не угрожал другому) и о ходе коня (поиск маршрута, который проводит коня через каждую клетку доски NxN, не посещая ни одну клетку дважды).338339### Передача значений в генератор340341В Python 2.4 и ранее генераторы только выдавали результат. Как только код генератора вызывался для создания итератора, не было способа передать новую информацию в функцию при возобновлении её выполнения. Можно было сымитировать эту возможность, заставив генератор обращаться к глобальной переменной или передавая изменяемый объект, который затем изменяли вызывающие стороны, но эти подходы были громоздкими.342343В Python 2.5 появился простой способ передачи значений в генератор. [`yield`](https://python-all.ru/3.15/reference/simple_stmts.html#yield) стал выражением, возвращающим значение, которое можно присвоить переменной или иным образом обработать:344345```python346val = (yield i)347```348349Рекомендуется **всегда** заключать выражение `yield` в скобки, когда вы что-то делаете с возвращаемым значением, как в примере выше. Скобки не всегда обязательны, но проще всегда их добавлять, чем запоминать, когда они нужны.350351([**PEP 342**](https://python-all.ru/3.15/howto/functional.html) объясняет точные правила: выражение `yield` всегда должно быть в скобках, за исключением случая, когда оно находится на верхнем уровне справа от присваивания. Это значит, что можно написать `val = yield i`, но нужно использовать скобки при наличии операции, как в `val = (yield i) + 12`.)352353Значения отправляются в генератор вызовом его метода [`send(value)`](https://python-all.ru/3.15/reference/expressions.html#generator.send). Этот метод возобновляет код генератора, и выражение `yield` возвращает указанное значение. Если вызывается обычный метод [`__next__()`](https://python-all.ru/3.15/reference/expressions.html#generator.__next__), то `yield` возвращает `None`.354355Вот простой счётчик, который увеличивается на 1 и позволяет изменять значение внутреннего счётчика.356357```python358def counter(maximum):359    i = 0360    while i < maximum:361        val = (yield i)362        # Если передано значение, изменить counter.363        if val is not None:364            i = val365        else:366            i += 1367```368369А вот пример изменения счётчика:370371```python372>>> it = counter(10)373>>> next(it)3740375>>> next(it)3761377>>> it.send(8)3788379>>> next(it)3809381>>> next(it)382Traceback (most recent call last):383  File "t.py", line 15, in <module>384    it.next()385StopIteration386```387388Поскольку `yield` часто будет возвращать `None`, всегда следует проверять этот случай. Не используйте его значение в выражениях, если вы не уверены, что метод [`send()`](https://python-all.ru/3.15/reference/expressions.html#generator.send) будет единственным методом, используемым для возобновления вашей функции-генератора.389390Помимо [`send()`](https://python-all.ru/3.15/reference/expressions.html#generator.send), у генераторов есть два других метода:391392- [`throw(value)`](https://python-all.ru/3.15/reference/expressions.html#generator.throw) используется для возбуждения исключения внутри генератора; исключение возбуждается выражением `yield`, на котором приостановлено выполнение генератора.393- [`close()`](https://python-all.ru/3.15/reference/expressions.html#generator.close) отправляет исключение [`GeneratorExit`](https://python-all.ru/3.15/library/exceptions.html#GeneratorExit) генератору для завершения итерации. При получении этого исключения код генератора должен либо возбудить `GeneratorExit`, либо [`StopIteration`](https://python-all.ru/3.15/library/exceptions.html#StopIteration); перехватывать исключение и делать что-либо ещё запрещено и вызовет [`RuntimeError`](https://python-all.ru/3.15/library/exceptions.html#RuntimeError). `close()` также будет вызван сборщиком мусора Python при сборке генератора.394395  Если нужно выполнить код очистки при возникновении [`GeneratorExit`](https://python-all.ru/3.15/library/exceptions.html#GeneratorExit), рекомендуется использовать конструкцию `try: ... finally:` вместо перехвата `GeneratorExit`.396397Совокупный эффект этих изменений – превращение генераторов из однонаправленных производителей информации одновременно и в производителей, и в потребителей.398399Генераторы также становятся **корутинами** – более обобщённой формой подпрограмм. Подпрограммы имеют одну точку входа и одну точку выхода (начало функции и оператор `return`), тогда как корутины могут иметь много разных точек входа, выхода и возобновления (операторы `yield`).400401## Встроенные функции402403Рассмотрим подробнее встроенные функции, часто используемые с итераторами.404405Две встроенные функции Python, [`map()`](https://python-all.ru/3.15/library/functions.html#map) и [`filter()`](https://python-all.ru/3.15/library/functions.html#filter), дублируют возможности генераторных выражений:406407**[`map(f, iterA, iterB, ...)`](https://python-all.ru/3.15/library/functions.html#map) возвращает итератор по последовательности**408409`f(iterA[0], iterB[0]), f(iterA[1], iterB[1]), f(iterA[2], iterB[2]), ...`.410411```python412>>> def upper(s):413...     return s.upper()414```415416```python417>>> list(map(upper, ['sentence', 'fragment']))418['SENTENCE', 'FRAGMENT']419>>> [upper(s) for s in ['sentence', 'fragment']]420['SENTENCE', 'FRAGMENT']421```422423Конечно, того же эффекта можно достичь с помощью спискового включения.424425[`filter(predicate, iter)`](https://python-all.ru/3.15/library/functions.html#filter) возвращает итератор по всем элементам последовательности, удовлетворяющим определённому условию, и также дублируется списковыми включениями. **Предикат** – это функция, возвращающая истинностное значение некоторого условия; для использования с `filter()` предикат должен принимать одно значение.426427```python428>>> def is_even(x):429...     return (x % 2) == 0430```431432```python433>>> list(filter(is_even, range(10)))434[0, 2, 4, 6, 8]435```436437Это также можно записать в виде спискового включения:438439```python440>>> list(x for x in range(10) if is_even(x))441[0, 2, 4, 6, 8]442```443444[`enumerate(iter, start=0)`](https://python-all.ru/3.15/library/functions.html#enumerate) нумерует элементы в итерируемом объекте, возвращая кортежи из двух элементов: счёт (от *начала*) и каждый элемент.445446```python447>>> for item in enumerate(['subject', 'verb', 'object']):448...     print(item)449(0, 'subject')450(1, 'verb')451(2, 'object')452```453454[`enumerate()`](https://python-all.ru/3.15/library/functions.html#enumerate) часто используется при переборе списка и записи индексов, для которых выполняются определённые условия:455456```python457f = open('data.txt', 'r')458for i, line in enumerate(f):459    if line.strip() == '':460        print('Blank line at line #%i' % i)461```462463[`sorted(iterable, key=None, reverse=False)`](https://python-all.ru/3.15/library/functions.html#sorted) собирает все элементы итерируемого объекта в список, сортирует его и возвращает отсортированный результат. Аргументы *key* и *reverse* передаются методу [`sort()`](https://python-all.ru/3.15/library/stdtypes.html#list.sort) созданного списка.464465```python466>>> import random467>>> # Сгенерировать 8 случайных чисел в диапазоне [0, 10000).468>>> rand_list = random.sample(range(10000), 8)469>>> rand_list470[769, 7953, 9828, 6431, 8442, 9878, 6213, 2207]471>>> sorted(rand_list)472[769, 2207, 6213, 6431, 7953, 8442, 9828, 9878]473>>> sorted(rand_list, reverse=True)474[9878, 9828, 8442, 7953, 6431, 6213, 2207, 769]475```476477(Более подробно сортировка рассматривается в разделе [Техники сортировки](https://python-all.ru/3.15/howto/sorting.html#sortinghowto).)478479Встроенные функции [`any(iter)`](https://python-all.ru/3.15/library/functions.html#any) и [`all(iter)`](https://python-all.ru/3.15/library/functions.html#all) проверяют истинность элементов итерируемого объекта. `any()` возвращает `True`, если хотя бы один элемент является истинным, а `all()` возвращает `True`, если все элементы истинны:480481```python482>>> any([0, 1, 0])483True484>>> any([0, 0, 0])485False486>>> any([1, 1, 1])487True488>>> all([0, 1, 0])489False490>>> all([0, 0, 0])491False492>>> all([1, 1, 1])493True494```495496[`zip(iterA, iterB, ...)`](https://python-all.ru/3.15/library/functions.html#zip) берёт по одному элементу из каждого итерируемого объекта и возвращает их в виде кортежа:497498```python499zip(['a', 'b', 'c'], (1, 2, 3)) =>500  ('a', 1), ('b', 2), ('c', 3)501```502503Он не создаёт список в памяти и не исчерпывает все входные итераторы до возврата; вместо этого кортежи создаются и возвращаются только по запросу. (Технический термин для такого поведения – [ленивые вычисления](https://python-all.ru/3.15/howto/functional.html).)504505Этот итератор предназначен для использования с итерируемыми объектами одинаковой длины. Если длины различаются, результирующий поток данных будет иметь длину кратчайшего итерируемого объекта.506507```python508zip(['a', 'b'], (1, 2, 3)) =>509  ('a', 1), ('b', 2)510```511512Тем не менее, так делать не стоит, поскольку из более длинных итераторов может быть взят и отброшен элемент. Это означает, что использовать эти итераторы дальше нельзя – есть риск пропустить отброшенный элемент.513514## Модуль itertools515516Модуль [`itertools`](https://python-all.ru/3.15/library/itertools.html#module-itertools) содержит ряд часто используемых итераторов, а также функции для комбинирования нескольких итераторов. В этом разделе будут представлены возможности модуля на небольших примерах.517518Функции модуля делятся на несколько широких категорий:519520- Функции, создающие новый итератор на основе существующего.521- Функции для передачи элементов итератора в качестве аргументов функции.522- Функции для выбора части вывода итератора.523- Функция для группировки вывода итератора.524525### Создание новых итераторов526527[`itertools.count(start, step)`](https://python-all.ru/3.15/library/itertools.html#itertools.count) возвращает бесконечный поток данных с равномерно распределёнными значениями. При желании можно указать начальное число (по умолчанию 0) и интервал между числами (по умолчанию 1):528529```python530itertools.count() =>531  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...532itertools.count(10) =>533  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...534itertools.count(10, 5) =>535  10, 15, 20, 25, 30, 35, 40, 45, 50, 55, ...536```537538[`itertools.cycle(iter)`](https://python-all.ru/3.15/library/itertools.html#itertools.cycle) сохраняет копию содержимого переданного итерируемого объекта и возвращает новый итератор, который выдает его элементы от первого до последнего. Новый итератор будет повторять эти элементы бесконечно.539540```python541itertools.cycle([1, 2, 3, 4, 5]) =>542  1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...543```544545[`itertools.repeat(elem, [n])`](https://python-all.ru/3.15/library/itertools.html#itertools.repeat) возвращает переданный элемент *n* раз, или возвращает элемент бесконечно, если *n* не указан.546547```python548itertools.repeat('abc') =>549  abc, abc, abc, abc, abc, abc, abc, abc, abc, abc, ...550itertools.repeat('abc', 5) =>551  abc, abc, abc, abc, abc552```553554[`itertools.chain(iterA, iterB, ...)`](https://python-all.ru/3.15/library/itertools.html#itertools.chain) принимает произвольное количество итерируемых объектов и возвращает все элементы первого итератора, затем все элементы второго и т. д., пока все итерируемые объекты не будут исчерпаны.555556```python557itertools.chain(['a', 'b', 'c'], (1, 2, 3)) =>558  a, b, c, 1, 2, 3559```560561[`itertools.islice(iter, [start], stop, [step])`](https://python-all.ru/3.15/library/itertools.html#itertools.islice) возвращает поток данных, представляющий собой срез итератора. С одним аргументом *stop* он вернёт первые *stop* элементов. Если указан начальный индекс, будет получено *stop-start* элементов, а если указано значение *step*, элементы будут пропускаться соответствующим образом. В отличие от срезов строк и списков Python, для *start*, *stop* и *step* нельзя использовать отрицательные значения.562563```python564itertools.islice(range(10), 8) =>565  0, 1, 2, 3, 4, 5, 6, 7566itertools.islice(range(10), 2, 8) =>567  2, 3, 4, 5, 6, 7568itertools.islice(range(10), 2, 8, 2) =>569  2, 4, 6570```571572[`itertools.tee(iter, [n])`](https://python-all.ru/3.15/library/itertools.html#itertools.tee) реплицирует итератор: возвращает *n* независимых итераторов, каждый из которых будет возвращать содержимое исходного итератора. Если не указано значение *n*, по умолчанию используется 2. Репликация итераторов требует сохранения части содержимого исходного итератора, поэтому при большом размере итератора и неравномерном потреблении новых итераторов может расходоваться значительный объём памяти.573574```python575itertools.tee( itertools.count() ) =>576   iterA, iterB577578where iterA ->579   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...580581and   iterB ->582   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...583```584585### Применение функций к элементам586587Модуль [`operator`](https://python-all.ru/3.15/library/operator.html#module-operator) содержит набор функций, соответствующих операторам Python. Примеры: [`operator.add(a, b)`](https://python-all.ru/3.15/library/operator.html#operator.add) (складывает два значения), [`operator.ne(a, b)`](https://python-all.ru/3.15/library/operator.html#operator.ne) (аналог `a != b`) и [`operator.attrgetter('id')`](https://python-all.ru/3.15/library/operator.html#operator.attrgetter) (возвращает вызываемый объект, который извлекает атрибут `.id`).588589[`itertools.starmap(func, iter)`](https://python-all.ru/3.15/library/itertools.html#itertools.starmap) предполагает, что итерируемый объект будет возвращать поток данных из кортежей, и вызывает *func*, используя эти кортежи в качестве аргументов:590591```python592itertools.starmap(os.path.join,593                  [('/bin', 'python'), ('/usr', 'bin', 'java'),594                   ('/usr', 'bin', 'perl'), ('/usr', 'bin', 'ruby')])595=>596  /bin/python, /usr/bin/java, /usr/bin/perl, /usr/bin/ruby597```598599### Выбор элементов600601Ещё одна группа функций выбирает подмножество элементов итератора на основе предиката.602603[`itertools.filterfalse(predicate, iter)`](https://python-all.ru/3.15/library/itertools.html#itertools.filterfalse) является противоположностью [`filter()`](https://python-all.ru/3.15/library/functions.html#filter), возвращая все элементы, для которых предикат возвращает ложь:604605```python606itertools.filterfalse(is_even, itertools.count()) =>607  1, 3, 5, 7, 9, 11, 13, 15, ...608```609610[`itertools.takewhile(predicate, iter)`](https://python-all.ru/3.15/library/itertools.html#itertools.takewhile) возвращает элементы до тех пор, пока предикат возвращает истину. Как только предикат возвращает ложь, итератор сигнализирует об окончании результатов.611612```python613def less_than_10(x):614    return x < 10615616itertools.takewhile(less_than_10, itertools.count()) =>617  0, 1, 2, 3, 4, 5, 6, 7, 8, 9618619itertools.takewhile(is_even, itertools.count()) =>620  0621```622623[`itertools.dropwhile(predicate, iter)`](https://python-all.ru/3.15/library/itertools.html#itertools.dropwhile) отбрасывает элементы, пока предикат возвращает истину, а затем возвращает остальные результаты итерируемого объекта.624625```python626itertools.dropwhile(less_than_10, itertools.count()) =>627  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...628629itertools.dropwhile(is_even, itertools.count()) =>630  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...631```632633[`itertools.compress(data, selectors)`](https://python-all.ru/3.15/library/itertools.html#itertools.compress) принимает два итератора и возвращает только те элементы *data*, для которых соответствующий элемент *selectors* является истинным, останавливаясь, когда любой из них исчерпан:634635```python636itertools.compress([1, 2, 3, 4, 5], [True, True, False, False, True]) =>637   1, 2, 5638```639640### Комбинаторные функции641642[`itertools.combinations(iterable, r)`](https://python-all.ru/3.15/library/itertools.html#itertools.combinations) возвращает итератор, выдающий все возможные комбинации по *r* элементов из *iterable*.643644```python645itertools.combinations([1, 2, 3, 4, 5], 2) =>646  (1, 2), (1, 3), (1, 4), (1, 5),647  (2, 3), (2, 4), (2, 5),648  (3, 4), (3, 5),649  (4, 5)650651itertools.combinations([1, 2, 3, 4, 5], 3) =>652  (1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5),653  (2, 3, 4), (2, 3, 5), (2, 4, 5),654  (3, 4, 5)655```656657Элементы в каждом кортеже остаются в том же порядке, в котором их вернул *iterable*. Например, в примерах выше число 1 всегда стоит перед 2, 3, 4 или 5. Похожая функция [`itertools.permutations(iterable, r=None)`](https://python-all.ru/3.15/library/itertools.html#itertools.permutations) снимает это ограничение на порядок и возвращает все возможные перестановки длины *r*:658659```python660itertools.permutations([1, 2, 3, 4, 5], 2) =>661  (1, 2), (1, 3), (1, 4), (1, 5),662  (2, 1), (2, 3), (2, 4), (2, 5),663  (3, 1), (3, 2), (3, 4), (3, 5),664  (4, 1), (4, 2), (4, 3), (4, 5),665  (5, 1), (5, 2), (5, 3), (5, 4)666667itertools.permutations([1, 2, 3, 4, 5]) =>668  (1, 2, 3, 4, 5), (1, 2, 3, 5, 4), (1, 2, 4, 3, 5),669  ...670  (5, 4, 3, 2, 1)671```672673Если не указать значение *r*, используется длина итерируемого объекта, то есть переставляются все элементы.674675Обратите внимание: эти функции порождают все возможные комбинации по позициям и не требуют, чтобы содержимое *iterable* было уникальным:676677```python678itertools.permutations('aba', 3) =>679  ('a', 'b', 'a'), ('a', 'a', 'b'), ('b', 'a', 'a'),680  ('b', 'a', 'a'), ('a', 'a', 'b'), ('a', 'b', 'a')681```682683Одинаковый кортеж `('a', 'a', 'b')` встречается дважды, но две строки 'a' пришли из разных позиций.684685[`itertools.combinations_with_replacement(iterable, r)`](https://python-all.ru/3.15/library/itertools.html#itertools.combinations_with_replacement) ослабляет другое ограничение: элементы могут повторяться в одном кортеже. Концептуально для первого места каждого кортежа выбирается элемент, который затем заменяется перед выбором второго.686687```python688itertools.combinations_with_replacement([1, 2, 3, 4, 5], 2) =>689  (1, 1), (1, 2), (1, 3), (1, 4), (1, 5),690  (2, 2), (2, 3), (2, 4), (2, 5),691  (3, 3), (3, 4), (3, 5),692  (4, 4), (4, 5),693  (5, 5)694```695696### Группировка элементов697698Последняя функция, которую я рассмотрю, [`itertools.groupby(iter, key_func=None)`](https://python-all.ru/3.15/library/itertools.html#itertools.groupby), самая сложная. `key_func(elem)` – это функция, которая может вычислить ключевое значение для каждого элемента, возвращённого итерируемым объектом. Если не передать функцию-ключ, ключом будет каждый элемент сам по себе.699700[`groupby()`](https://python-all.ru/3.15/library/itertools.html#itertools.groupby) собирает все последовательные элементы из базового итерируемого объекта, имеющие одинаковое ключевое значение, и возвращает поток кортежей из двух элементов, содержащих ключевое значение и итератор для элементов с этим ключом.701702```python703city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'),704             ('Anchorage', 'AK'), ('Nome', 'AK'),705             ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ'),706             ...707            ]708709def get_state(city_state):710    return city_state[1]711712itertools.groupby(city_list, get_state) =>713  ('AL', iterator-1),714  ('AK', iterator-2),715  ('AZ', iterator-3), ...716717where718iterator-1 =>719  ('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL')720iterator-2 =>721  ('Anchorage', 'AK'), ('Nome', 'AK')722iterator-3 =>723  ('Flagstaff', 'AZ'), ('Phoenix', 'AZ'), ('Tucson', 'AZ')724```725726[`groupby()`](https://python-all.ru/3.15/library/itertools.html#itertools.groupby) предполагает, что содержимое базового итерируемого объекта будет уже отсортировано по ключу. Обратите внимание: возвращённые итераторы также используют базовый итерируемый объект, поэтому нужно потребить результаты iterator-1 до того, как запрашивать iterator-2 и соответствующий ему ключ.727728## Модуль functools729730Модуль [`functools`](https://python-all.ru/3.15/library/functools.html#module-functools) содержит некоторые функции высшего порядка. **Функция высшего порядка** принимает одну или несколько функций на вход и возвращает новую функцию. Самый полезный инструмент в этом модуле – функция [`functools.partial()`](https://python-all.ru/3.15/library/functools.html#functools.partial).731732Для программ в функциональном стиле иногда требуется создавать варианты существующих функций с частично заполненными параметрами. Рассмотрим функцию Python `f(a, b, c)`; возможно, вы захотите создать новую функцию `g(b, c)`, эквивалентную `f(1, b, c)`; вы заполняете значение для одного из параметров `f()`. Это называется «частичным применением функции».733734Конструктор [`partial()`](https://python-all.ru/3.15/library/functools.html#functools.partial) принимает аргументы `(function, arg1, arg2, ..., kwarg1=value1, kwarg2=value2)`. Полученный объект является вызываемым, поэтому его можно просто вызвать для выполнения `function` с переданными аргументами.735736Вот небольшой, но реалистичный пример:737738```python739import functools740741def log(message, subsystem):742    """Записать содержимое 'message' в указанную подсистему."""743    print('%s: %s' % (subsystem, message))744    ...745746server_log = functools.partial(log, subsystem='server')747server_log('Unable to open socket')748```749750[`functools.reduce(func, iter, [initial_value])`](https://python-all.ru/3.15/library/functools.html#functools.reduce) кумулятивно выполняет операцию над всеми элементами итерируемого объекта и, следовательно, не может применяться к бесконечным итерируемым объектам. *func* должна быть функцией, принимающей два элемента и возвращающей одно значение. `functools.reduce()` берёт первые два элемента A и B, возвращённые итератором, и вычисляет `func(A, B)`. Затем запрашивает третий элемент C, вычисляет `func(func(A, B), C)`, объединяет этот результат с четвёртым элементом и продолжает до исчерпания итерируемого объекта. Если итерируемый объект не возвращает ни одного значения, возбуждается исключение [`TypeError`](https://python-all.ru/3.15/library/exceptions.html#TypeError). Если указано начальное значение, оно используется как отправная точка и `func(initial_value, A)` становится первым вычислением.751752```python753>>> import operator, functools754>>> functools.reduce(operator.concat, ['A', 'BB', 'C'])755'ABBC'756>>> functools.reduce(operator.concat, [])757Traceback (most recent call last):758  ...759TypeError: reduce() of empty iterable with no initial value760>>> functools.reduce(operator.mul, [1, 2, 3], 1)7616762>>> functools.reduce(operator.mul, [], 1)7631764```765766Если использовать [`operator.add()`](https://python-all.ru/3.15/library/operator.html#operator.add) с [`functools.reduce()`](https://python-all.ru/3.15/library/functools.html#functools.reduce), будут просуммированы все элементы итерируемого объекта. Этот случай настолько распространён, что для него существует специальная встроенная функция [`sum()`](https://python-all.ru/3.15/library/functions.html#sum):767768```python769>>> import functools, operator770>>> functools.reduce(operator.add, [1, 2, 3, 4], 0)77110772>>> sum([1, 2, 3, 4])77310774>>> sum([])7750776```777778Однако для многих применений [`functools.reduce()`](https://python-all.ru/3.15/library/functools.html#functools.reduce) может быть понятнее просто написать очевидный цикл [`for`](https://python-all.ru/3.15/reference/compound_stmts.html#for):779780```python781import functools782# Вместо:783product = functools.reduce(operator.mul, [1, 2, 3], 1)784785# Можно написать:786product = 1787for i in [1, 2, 3]:788    product *= i789```790791Родственная функция – [`itertools.accumulate(iterable, func=operator.add)`](https://python-all.ru/3.15/library/itertools.html#itertools.accumulate). Она выполняет то же вычисление, но вместо возврата только конечного результата `accumulate()` возвращает итератор, который также выдаёт каждый промежуточный результат:792793```python794itertools.accumulate([1, 2, 3, 4, 5]) =>795  1, 3, 6, 10, 15796797itertools.accumulate([1, 2, 3, 4, 5], operator.mul) =>798  1, 2, 6, 24, 120799```800801### Модуль operator802803Модуль [`operator`](https://python-all.ru/3.15/library/operator.html#module-operator) упоминался ранее. Он содержит набор функций, соответствующих операторам Python. Эти функции часто полезны в функциональном коде, поскольку избавляют от написания тривиальных функций, выполняющих одну операцию.804805Некоторые функции этого модуля:806807- Математические операции: `add()`, `sub()`, `mul()`, `floordiv()`, `abs()`, …808- Логические операции: `not_()`, `truth()`.809- Побитовые операции: `and_()`, `or_()`, `invert()`.810- Сравнения: `eq()`, `ne()`, `lt()`, `le()`, `gt()` и `ge()`.811- Идентичность объектов: `is_()`, `is_not()`.812813Полный список – в документации модуля operator.814815## Небольшие функции и лямбда-выражение816817При написании программ в функциональном стиле часто требуются небольшие функции, которые выступают в роли предикатов или каким-либо образом комбинируют элементы.818819Если есть подходящая встроенная функция Python или функция из модуля, не нужно определять новую функцию:820821```python822stripped_lines = [line.strip() for line in lines]823existing_files = filter(os.path.exists, file_list)824```825826Если нужной функции не существует, её нужно написать. Один из способов написания небольших функций – использовать выражение [`lambda`](https://python-all.ru/3.15/reference/expressions.html#lambda). `lambda` принимает несколько параметров и выражение, объединяющее эти параметры, и создаёт анонимную функцию, возвращающую значение выражения:827828```python829adder = lambda x, y: x+y830831print_assign = lambda name, value: name + '=' + str(value)832```833834Альтернатива – просто использовать инструкцию `def` и определить функцию обычным способом:835836```python837def adder(x, y):838    return x + y839840def print_assign(name, value):841    return name + '=' + str(value)842```843844Какая альтернатива предпочтительнее? Это вопрос стиля; я обычно стараюсь не использовать `lambda`.845846Одна из причин моего предпочтения в том, что `lambda` весьма ограничен в определении функций. Результат должен быть вычислим как одно выражение, а это значит, что нельзя использовать множественные сравнения `if... elif... else` или инструкции `try... except`. Если попытаться сделать слишком много в инструкции `lambda`, получится чрезмерно сложное выражение, которое трудно читать. Быстро, что делает следующий код?847848```python849import functools850total = functools.reduce(lambda a, b: (0, a[1] + b[1]), items)[1]851```852853Можно разобраться, но требуется время, чтобы распутать выражение и понять, что происходит. Использование коротких вложенных инструкций `def` немного улучшает ситуацию:854855```python856import functools857def combine(a, b):858    return 0, a[1] + b[1]859860total = functools.reduce(combine, items)[1]861```862863Но лучше всего было бы просто использовать цикл `for`:864865```python866total = 0867for a, b in items:868    total += b869```870871Или встроенную функцию [`sum()`](https://python-all.ru/3.15/library/functions.html#sum) и выражение-генератор:872873```python874total = sum(b for a, b in items)875```876877Многие применения [`functools.reduce()`](https://python-all.ru/3.15/library/functools.html#functools.reduce) становятся понятнее, если записать их в виде циклов `for`.878879Фредрик Лунд однажды предложил следующий набор правил для рефакторинга использования `lambda`:8808811. Написать лямбда-функцию.8822. Написать комментарий, объясняющий, что, черт возьми, делает эта лямбда.8833. Некоторое время изучать комментарий и придумать имя, отражающее суть комментария.8844. Преобразовать лямбду в инструкцию def, используя это имя.8855. Удалить комментарий.886887Мне очень нравятся эти правила, но вы вольны не соглашаться с тем, что этот стиль без лямбда лучше.888889## История изменений и благодарности890891Это руководство было первоначально написано А. М. Куклингом. Автор хотел бы поблагодарить следующих людей за предложения, исправления и помощь с различными черновиками этой статьи: Иэн Бикинг, Ник Коглан, Ник Эффорд, Рэймонд Хеттингер, Джим Джуитт, Майк Крелл, Леандро Ламеро, Юсси Салмела, Коллин Уинтер, Блейк Уинтон.892893Версия 0.1: опубликована 30 июня 2006 г.894895Версия 0.11: опубликована 1 июля 2006 г. Исправлены опечатки.896897Версия 0.2: опубликована 10 июля 2006 г. Разделы о выражениях-генераторах и списковых включениях объединены в один. Исправлены опечатки.898899Версия 0.21: добавлены дополнительные ссылки, предложенные в списке рассылки tutor.900901Версия 0.30: добавлен раздел о модуле `functional`, написанный Коллином Уинтером; добавлен короткий раздел о модуле operator; несколько других правок.902903## Ссылки904905### Общие906907**Структура и интерпретация компьютерных программ**, Гарольд Абельсон, Джеральд Джей Сассман с Джули Сассман. Книгу можно найти по адресу [https://mitpress.mit.edu/sicp](https://python-all.ru/3.15/howto/functional.html). В этом классическом учебнике по информатике главы 2 и 3 обсуждают использование последовательностей и потоков для организации потока данных внутри программы. В книге используются примеры на Scheme, но многие подходы к проектированию, описанные в этих главах, применимы к коду Python в функциональном стиле.908909[https://defmacro.org/2006/06/19/fp.html](https://python-all.ru/3.15/howto/functional.html): общее введение в функциональное программирование с примерами на Java и длинным историческим вступлением.910911[https://en.wikipedia.org/wiki/Functional\_programming](https://python-all.ru/3.15/howto/functional.html): общая статья в Википедии, описывающая функциональное программирование.912913[https://en.wikipedia.org/wiki/Coroutine](https://python-all.ru/3.15/howto/functional.html): статья о корутинах.914915[https://en.wikipedia.org/wiki/Partial\_application](https://python-all.ru/3.15/howto/functional.html): статья о концепции частичного применения функций.916917[https://en.wikipedia.org/wiki/Currying](https://python-all.ru/3.15/howto/functional.html): статья о концепции каррирования.918919### Специфическое для Python920921[https://gnosis.cx/TPiP/](https://python-all.ru/3.15/howto/functional.html): первая глава книги Дэвида Мертца *Text Processing in Python* обсуждает функциональное программирование для обработки текста в разделе «Использование функций высшего порядка в обработке текстов».922923Мертц также написал серию из трёх статей по функциональному программированию для сайта IBM DeveloperWorks; см. [часть 1](https://python-all.ru/3.15/howto/functional.html), [часть 2](https://python-all.ru/3.15/howto/functional.html) и [часть 3](https://python-all.ru/3.15/howto/functional.html).924925### Документация Python926927Документация по модулю [`itertools`](https://python-all.ru/3.15/library/itertools.html#module-itertools).928929Документация по модулю [`functools`](https://python-all.ru/3.15/library/functools.html#module-functools).930931Документация по модулю [`operator`](https://python-all.ru/3.15/library/operator.html#module-operator).932933[**PEP 289**](https://python-all.ru/3.15/howto/functional.html): “Генераторные выражения”934935[**PEP 342**](https://python-all.ru/3.15/howto/functional.html): “Корутины через улучшенные генераторы” описывает новые возможности генераторов в Python 2.5.936