что делает lambda в python
Все, что нужно знать о lambda-функциях в Python
В этой статье вы узнаете о том, что такое лямбда-функции в Python. На самом деле, если вы знаете, что такое функции и умеете с ними работать, то знаете и что такое лямбда.
Что такое лямбда в Python?
Прежде чем переходить как разбору понятия лямбда в Python, попробуем понять, чем является обычная функция Python на более глубоком уровне.
Для этого потребуется немного поменять направление мышление. Как вы знаете, все в Python является объектом.
Например, когда мы запускаем эту простейшую строку кода
А что происходит при определении вот такой функции:
Это же значит, что функции можно передать в качестве аргументов другим функциям или даже использовать их как тип возвращаемого значения.
Рассмотрим простой пример, где функция f передается другой функции.
Попробуйте разобраться самостоятельно с тем, что делает этот код, прежде чем читать дальше.
Итак, modify_list — это функция, которая принимает список L и функцию fn в качестве аргументов. Затем она перебирает список элемент за элементом и применяет функцию к каждому из них.
Но можно передать и любую другую, которая изменит оригинальный список другим способом. Это очень мощный инструмент.
Теперь, когда с основами разобрались, стоит перейти к лямбда. Лямбда в Python — это просто еще один способ определения функции. Вот базовый синтаксис лямбда-функции в Python:
Лямбда принимает любое количество аргументов (или ни одного), но состоит из одного выражения. Возвращаемое значение — значение, которому присвоена функция. Например, если нужно определить функцию f из примера выше, то это можно сделать вот так:
Но возникает вопрос: а зачем нужны лямбда-функции, если их можно объявлять традиционным образом? Но на самом деле, они полезны лишь в том случае, когда нужна одноразовая функция. Такие функции еще называют анонимными. И, как вы увидите дальше, есть масса ситуаций, где они оказываются нужны.
Лямбда с несколькими аргументами
Определить лямбда-функцию с одним аргументом не составляет труда.
А если их должно быть несколько, то достаточно лишь разделить значения запятыми. Предположим, что нужна функция, которая берет два числовых аргумента и возвращает их произведение.
Отлично! А как насчет лямбда-функции без аргументов?
Лямбда-функция без аргументов
Несколько лямбда-функций
В определенный момент возникнет вопрос: а можно ли иметь лямбда-функцию из нескольких строк.
Ответ однозначен: нет.
Лямбда-функции в Python всегда принимают только одно выражение. Если же их несколько, то лучше создать обычную функцию.
Примеры лямбда-функций
Теперь рассмотрим самые распространенные примеры использования лямбда-функций.
Лямбда-функция и map
Распространенная операция со списками в Python — применение операции к каждому элементу.
map() — это встроенная функция Python, принимающая в качестве аргумента функцию и последовательность. Она работает так, что применяет переданную функцию к каждому элементу.
Так, вместо определения функции и передачи ее в map в качестве аргумента, можно просто использовать лямбда для быстрого определения ее прямо внутри. В этом есть смысл, если упомянутая функция больше не будет использоваться в коде.
Вот еще один пример.
Лямбда-функция и filter
filter() — это еще одна встроенная функция, которая фильтрует последовательность итерируемого объекта.
Другими словами, функция filter отфильтровывает некоторые элементы итерируемого объекта (например, списка) на основе какого-то критерия. Критерий определяется за счет передачи функции в качестве аргумента. Она же применяется к каждому элементу объекта.
С лямбда-функциями это все можно сделать максимально сжато. Код выше можно преобразовать в такой, написанный в одну строку.
И в этом сила лямбда-функций.
Лямбда-функция и сортировка списков
Теперь создадим экземпляры этого класса и добавим их в список.
Предположим, что мы хотим отсортировать его на основе поля age сотрудников. Вот что нужно сделать для этого:
Пара слов о выражениях и инструкциях
Как уже упоминалось, лямбда могут иметь только одно выражение (expression) в теле.
Обратите внимание, что речь идет не об инструкции (statement).
Выражение и инструкции — две разные вещи, в которых часто путаются. В программировании инструкцией является строка кода, выполняющая что-то, но не генерирующая значение.
Например, инструкция if или циклы for и while являются примерами инструкций. Заменить инструкцию на значение попросту невозможно.
А вот выражения — это значения. Запросто можно заменить все выражения в программе на значения, и программа продолжит работать корректно.
Тело лямбда-функции должно являться выражением, поскольку его значение будет тем, что она вернет. Обязательно запомните это для работы с лямбда-функциями в будущем.
Lambda-функции в Python с их синтаксисом и примерами
1. Lambda-функции
В этом уроке мы изучим, что из себя представляют lambda-функции в Python с их синтаксисом и примерами. Кроме того, мы рассмотрим, как и когда следует объявлять lambda-функции, а также как их можно использовать вместе со встроенными функциями Python такими как reduce(), map() и filter().
Когда мы создаем функции в Python, мы всегда используем ключевое слово def. При этом, делая так, мы назначаем им определенное имя. Но иногда у нас может возникнуть потребность объявить функцию анонимно или мы можем захотеть использовать функцию только один раз. В таком случае определение функции может показаться лишним, и как раз здесь нам придут на помощь lambda-функции.
2. Что собой представляет lambda-функция в Python?
Lambda-функция позволяет нам определять функцию анонимно. Стоит отметить, что она является именно функцией, а не оператором. То есть лямбда-функция возвращает значение, и у нее есть неявный оператор return. Ниже приведен синтаксис lambda-функций в Python.
Lambda-функция, возвращает свое значение в том месте, в котором вы его объявляете.
Как объявить lambda-функцию?
Для того, чтобы объявить lambda-функцию, используйте ключевое слово lambda.
Вы можете назначить lambda-функцию переменной, если хотите использовать ее в дальнейшем.
В этом примере e является аргументом, а e-2 выражением.
После назначения переменной, вы можете вызвать данную lambda-функцию, как и любую другую функцию в Python. Попробуем это сделать, взяв в качестве аргумента целое число 1.
4. Чем на самом деле является выражение lambda-функции в Python
Как мы смогли убедиться, lambda в Python возвращает выражение. Но у нас может появится вопрос, что же собой представляет выражение? В общем смысле, выражение — это последовательность букв символов и чисел, возвращающая определенное значение. Например, к выражениям можно отнести следующие записи.
Но при этом, такие записи как назначение не могут быть определены как выражение для lambda-функции, так как они ничего не возвращают даже None.
5. Когда лучше использовать lambda-функцию в Python?
Как мы видели ранее, lambda-функция в Python может принимать несколько аргументов и одно выражение. Причем значение этого выражения как раз и есть то, что возвращается при вызове функции. Использование lambda-функций не является обязательным, но может оказаться полезным в определенных ситуациях. Таких, например как:
1. Когда у вас есть только одно выражение для исполнения в функции
Допустим, мы хотим для функции printhello() запрограммировать вывод на экран слова «Привет». Тогда в теле функции у нас будет всего одна строчка:
Теперь давайте сделаем то же самое, используя lambda-функцию.
Заметьте, что здесь мы не использовали никаких аргументов. Но вернемся и более подробно рассмотрим мы этот момент в дальнейшем, а пока давайте обратимся к еще одному примеру.
От редакции Pythonist. Рекомендуем статью «Функции и их аргументы в Python 3».
2. Когда нужно вызвать код только один раз
Одной из основных причин выделения функций из остального кода является необходимость в их многократном использовании. Но если вам нужно использовать какой-либо код не более чем один раз, вы можете прибегнуть к lambda-функциям, не объявляя для этого стандартную функцию.
6. Значения аргументов по умолчанию для lambda-функции в Python
В Python, как впрочем и в других языках, например, C++, мы можем задавать значения аргументов по умолчанию. Но как нам это может пригодиться? Допустим функция func1() принимает 2 параметра a и b. Что произойдет если пользователь программы передаст лишь один из этих параметров или даже ни одного? Как раз для того, чтобы избежать возможной ошибки, или облегчить работу пользователя, вы можете задать вашим параметрам значения по умолчанию.
Давайте рассмотрим пример.
Здесь значениями по умолчанию для a и b являются соответственно 2 и 3. Для того, чтобы задать значения по умолчанию для lambda-функции, запись будет следующей:
7. Синтаксис lambda-функции в Python
Мы уже посмотрели, как объявляетcя lambda-функция в Python, но, как и все, что состоит из частей, ее объявление предполагает различные варианты. Так давайте же посмотрим, что мы можем, а что не можем делать с lambda-функцией.
a. Аргументы в Python
Одна или несколько переменных, присутствующих в выражении могут быть объявлены заранее. Но если речь идет об аргументах, то их значение должно быть либо задано по умолчанию, либо передано при вызове функции.
Здесь отсутствуют значения и a и b
У переменной a все еще отсутствует значение
Наконец здесь, так как нет аргументов с отсутствующим значением, все отлично работает.
От редакции Pythonist. Рекомендуем статью «Обрабатываем исключения в Python: try и except».
b. Пропускаем аргументы
Указывать аргументы в lambda-функции не обязательно. Она отлично работает и без них.
Во втором примере давайте в качестве выражения используем функцию print()
Вывод напрашивается сам собой, — пропуск аргументов в lambda-функции является вполне приемлемым.
c. Пропускаем выражение
Теперь давайте попробуем запустить нашу функцию без выражения.
Ясное дело ничего не сработало, да и почему оно должно было сработать, если значение выражение это как раз и есть то, что функция возвращает? Без выражения функция не имеет никакого смысла.
8. Совместное использование Lambda-функции со встроенными функциями Python
Существуют некоторые встроенные функции в Python, такие например как filter() или map(), в которых мы можем использовать lambda-функции, для выполнения определенных преобразований. Давайте же рассмотрим их поподробнее.
Для начала возьмем список под названием numbers
затем возьмем lambda-функцию следующего содержания.
a. filter()
Функция filter() принимает два параметра — функцию и список для обработки. В нашем примере мы также применим функцию list(), чтобы преобразовать объект filter в список.
Вот окончательный код, который нам нужен, и можете сами попробовать догадаться, как он работает. Итак, он берет список numbers, и отфильтровывает все элементы из него, которые не делятся нацело на 3. При этом фильтрация никак не изменяет изначальный список.
От редакции Pythonist. Рекомендуем статью «Списки в Python: изменяемость, доступ к элементам».
b. map()
Функция map() в отличие от функции filter() возвращает значение выражения для каждого элемента в списке. Давайте посмотрим как это работает на уже знакомом нам списке numbers.
c. reduce()
Наконец функция reduce() принимает два параметра — функцию и список. Сперва она применяет стоящую первым аргументом функцию для двух начальных элементов списка, а затем использует в качестве аргументов этой функции полученное значение вместе со следующим элементом списка и так до тех пор, пока весь список не будет пройден, а итоговое значение не будет возвращено. Для того, чтобы использовать reduce(), вы должны сначала импортировать ее из модуля functools.
Давайте посмотрим как получился такой результат.
Таким образом, на выходе у нас получается 5.
Давайте теперь возьмем другой пример.
Если проделать то же самое для x+y, то у вас получится 55.
9. Заключение
На этом, пожалуй, все. В той статье мы с вами сперва изучили, чем является lambda-функция в Python, рассмотрели ее синтаксис, а также выяснили как ее создать. Далее мы рассмотрели, что из себя представляет выражение, а также как передать аргументы по умолчанию в выражение lambda. Наконец мы попробовали использовать lambda-функцию внутри трех встроенных функций filter(), map(), и reduce() и выяснили, что lambda в сравнении с обычными функциями имеет ряд преимуществ.
Если у вас появились какие-то вопросы, пожалуйста, оставляйте их в комментариях.
Функциональное программирование на Python для самых маленьких — Часть 1 — Lambda Функция
Я решил написать эту серию статей, ибо считаю, что никто не должен сталкиваться с той стеной непонимания, с которой столкнулся когда-то я.
Ведь большинство статей написаны таки образом что, для того чтобы понять что-то в Функциональном Программировании (далее ФП), тебе надо уже знать многое в ФП. Эту статью я старался написать максимально просто — настолько понятно, чтобы её суть мог уловить мой племянник, школьник, который сейчас делает свои первые шаги в Python.
Небольшое введение
Для начала, давайте разберемся, что такое функциональное программирование, в чем его особенности, зачем оно было придумано, а также где и как его использовать. Стоп… А зачем? Об этом написаны тонны материалов, да и в этой статье судя по всему эта информация не особо нужна. Эта статья написана для того, чтобы читатели научились разбираться в коде, который написан в функциональном стиле. Но если вы все-таки хотите разобраться в истории Функционального Программирования и в том, как оно работает под капотом, то советую вам почитать о таких вещах, как
Чистая Функция — Функция которая является детерминированной и не обладает никакими побочными эффектами.
То есть чтобы функция являлась чистой она должна быть детерминированной — то есть каждый раз при одинаковом наборе аргументов выдавать одинаковый результат.
Пример детерминированной функции
И пример не детерминированной:
Каждый раз при смене дня недели (который не является аргументом функции) функция выдает разные результаты.
Самый очевидный пример не детерминированной функции это random:
Второе важное качество чистой функции это отсутствие побочных эффектов.
Функция sort_by_sort имеет побочные эффекты потому что изменяет исходный список элементов и выводит что то в консоль.
В отличии от предыдущего примера функция sort_by_sorted не меняет исходного массива и возвращает результат не выводя его в консоль самостоятельно.
Чистые функции хороши тем что:
Функции высшего порядка — в программировании функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата.
С основами чуть чуть разобрались и теперь перейдем к следующему шагу.
Итак, начнем
Для начала надо понять следующее — что такое Функциональное Программирование вообще. Лично я знаю две самые часто упоминаемые парадигмы в повседневном программировании — это ООП и ФП.
Если упрощать совсем и объяснять на пальцах, то описать эти две парадигмы можно следующим образом:
Это относится и к ФП — взял какие-то данные, взял какую-то функцию, поигрался с ними и выдал что-то на выходе.
Не стану расписывать всё, иначе это будет оооочень долго. Цель данной статьи — помочь разобраться, а не объяснить, как и что работает, поэтому тут мы рассмотрим основные функции из ФП.
В большинстве своем ФП (как я его воспринимаю) — это просто упрощенное написание кода. Любой код, написанный в функциональном стиле, может быть довольно легко переписан в обычном стиле без потери качества, но более примитивно. Цель ФП заключается в том, чтобы писать код более простой, понятный и который легче поддерживать, а также который занимает меньше памяти, ну и куда же без этого — разумеется, главная вечная мораль программирования — DRY (Don’t Repeat Yourself — Не повторяйся).
Сейчас мы с вами разберем одну из основных функций, которая применяется в ФП — Lambda функцию.
В следующих статьях мы разберем такие функции как Map, Zip, Filter и Reduce.
Lambda функция
Lambda — это инструмент в python и других языках программирования для вызова анонимных функций. Многим это скорее всего ничего не скажет и никак не прояснит того, как она работает, поэтому я расскажу вам просто механизм работы lambda выражений.
Рассмотрим пример. Например, нам надо написать функцию которая бы считала площадь круга при известном радиусе.
Формула площади круга это
где
S — это площадь круга
pi — математическая константа равная 3.14 которую мы получим из стандартной библиотеки Math
r — радиус круга — единственная переменная которую мы будем передавать нашей функции
Теперь оформим это все в python:
Вроде бы неплохо, но это всё может выглядеть куда круче, если записывать это через lambda:
Чтобы было понятнее, анонимный вызов функции подразумевает то, что вы используете её, нигде не объявляя, как в примере выше.
Лямбда функция работает по следующему принципу
Рассмотрим пример с двумя входными аргументами. Например, нам надо посчитать объем конуса по следующей формуле:
Запишем это все в python:
А теперь как это будет выглядеть в lambda форме:
Количество переменных здесь никак не ограничено. Для примера посчитаем объем усеченного конуса, где у нас учитываются 3 разные переменные.
Объем усеченного конуса считается по формуле:
И вот, как это будет выглядеть в python классически:
А теперь покажем, как это будет выглядеть с lambda:
После того, как мы разобрались, как работает lambda функция, давайте разберем ещё кое-что интересное, что можно делать с помощью lambda функции, что может оказаться для вас весьма неожиданным — Сортировку.
Сортировать одномерные списки в python с помощью lambda довольно глупо — это будет выглядеть, как бряцание мускулами там, где оно совсем не нужно.
Ну серьезно допустим, у нас есть обычный список (не важно состоящий из строк или чисел) и нам надо его отсортировать — тут же проще всего использовать встроенную функцию sorted(). И в правду, давайте посмотрим на это.
В таких ситуациях, действительно, хватает обычного sorted() (ну или sort(), если вам нужно изменить текущий список на месте без создания нового, изменив исходный).
Но что, если нужно отсортировать список словарей по разным ключам? Тут может быть запись как в классическом стиле, так и в функциональном. Допустим, у нас есть список книг вселенной Песни Льда и Пламени с датами их публикаций и количеством страниц в них.
Как всегда, начнем с классической записи.
А теперь перепишем это все через lambda функцию:
Таким образом, lambda функция хорошо подходит для сортировки многомерных списков по разным параметрам.
Если вы повторите весь этот код самостоятельно, написав его сами, то я уверен, что с этого момента вы сможете сказать, что отныне вы понимаете, как работают lambda выражения, и сможете применять их в работе.
Но где же тут та самая экономия места, времени и памяти? Экономится максимум пара строк.
И вот тут мы подходим к реально интересным вещам.
Которые разберем в следующей статье, где мы обсудим map функцию.
UPD: По многочисленным просьбам, расставил знаки препинания.
Изучаем lambda-функции в Python
Оператор lambda это анонимная, или несвязанная функция, при этом довольно ограниченная. Давайте взглянем на несколько базовых примеров, и взглянем, можем ли мы найти применение данной функции. Обычно в таких примерах показывают унылые операции с дублированием. Чтобы сделать обучение интереснее, в нашем примере мы рассмотрим, как найти квадратный корень используя модуль math. Для начала, мы покажем обычную функцию, потом аналогичный с лямбдой-функцией:
Если вы используете каждую из этих функцию, вы закончите с запятой. Вот еще несколько примеров:
Довольно прозрачно. Но где найти применение лямбде в реальной жизни? Может в программе калькуляторе? Это будет работать, но это будет достаточно ограниченным приложением как для Python. Основную часть возможностей Python, которые демонстрирует лямбда можно увидеть в обратных вызовах Tkinter. Tkinter – это включенный в Python набор инструментов, для создания графических интерфейсов.
Tkinter + lambda
Мы начнем с Tkinter, так как он включен в стандартный пакет Python. Рассмотрим достаточно простой скрипт с тремя кнопками, две из которых связаны с обработчиком событий с использованием лямбды:
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Telegram Чат & Канал
Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Обратите внимание на переменные btn22 и btn44. Именно здесь происходят события. Мы создали экземпляр tk.Button и связываемся с нашим методом printNum одним махом. Наша лямбда присваивается параметру команды кнопки. Это значит, что мы создаем одноразовую функцию для команды, по аналогии с кнопкой выхода, где мы вызываем метод выхода из фрейма. Разница в том, что отдельная лямбда – это метод, который вызывает другой метод и передает ему целое число. В методе printNum, мы пишем в stdout о том, какая кнопка была нажата, пользуясь информацией, которая была передана функцией lambda. Улавливаете? Если да, то мы продолжим. Если нет – перечитайте данный параграф столько раз, сколько нужно для того, чтобы эта информация усвоилась, или пока не сойдете с ума, в любом случае, что-то должно произойти первым.
Подведем итоги
Оператор lambda, в том числе, используется во всех других проектах и операциях. Если вы загуглите фразу «Python lambda«, вам выдаст великое множество различных кодов. Например, если вы поищете «Django lambda», вы заметите, что в Django содержит набор моделей, которые используют лямбду. Плагин Elixir для SqlAlchemy также использует лямбду. Если вы будете обращать на это внимание, как часто вы будете натыкаться на этот маленький создатель функций.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
Основы функционального программирования на Python
Этот пост служит для того, чтобы освежить в памяти, а некоторых познакомить с базовыми возможностями функционального программирования на языке Python, а также дополнением к моему предыдущему посту о конвейере данных. Материал поста разбит на 5 частей:
Принципы функционального программирования
Включение в последовательность
Рекомендации по ФП на языке Python
Принципы функционального программирования
Функциональное программирование представляет собой методику написания программного обеспечения, в центре внимания которой находятся функции. Функции могут присваиваться переменным, они могут передаваться в другие функции и порождать новые функции. Python имеет богатый и мощный арсенал инструментов, которые облегчают разработку функционально-ориентированных программ.
В последние годы почти все известные процедурные и объектно-ориентированные языки программирования стали поддерживать средства функционального программирования (ФП). И язык Python не исключение.
Когда говорят о ФП, прежде всего имеют в виду следующее:
Функции – это «граждане более высокого сорта», т.е., все, что можно делать с «данными», можно делать и с функциями (в том числе передача функции другой функции в качестве аргумента).
Использование рекурсии в качестве основной структуры контроля потока управления. В некоторых языках не существует иной конструкции цикла, кроме рекурсии.
Акцент на обработке последовательностей. Списки с рекурсивным обходом подсписков часто используются в качестве замены циклов.
«Чистые» функциональные языки избегают побочных эффектов. Это исключает присваивания, почти повсеместно распространенный в императивных языках подход, при котором за одной и той же переменной последовательно закрепляются разные значения для отслеживания состояния программы.
ФП не одобряет или совершенно запрещает инструкции, используя вместо этого вычисление выражений (т.е. функций с аргументами). В предельном случае, одна программа есть одно выражение (плюс дополнительные определения).
ФП акцентируется на том, что должно быть вычислено, а не как.
Функциональное программирование представляет собой методику написания программного обеспечения, в центре внимания которой находятся функции. В парадигме ФП объектами первого класса являются функции. Они обрабатываются таким же образом, что и любой другой примитивный тип данных, такой как строковый и числовой. Функции могут получать другие функции в виде аргументов и на выходе возвращать новые функции. Функции, имеющие такие признаки, называются функциями более высокого порядка из-за их высокой выразительной мощи. И вам непременно следует воспользоваться их чудесной выразительностью.
Программистам чаще приходится работать с последовательностями значений, такими как списки и кортежи, или же контейнерами, такими как словари и множества. Как правило, в файлах хранятся большие объемы текстовых или числовых данных, которые затем загружаются в программу в соответствующие структуры данных и обрабатываются. Python имеет богатый и мощный арсенал инструментов, которые облегчают их обработку в функциональном стиле.
Далее будут представлены несколько таких встроенных функций.
Оператор lambda, функции map, filter, reduce и другие
Прежде чем продолжить, сначала следует познакомиться с еще одним ключевым словом языка Python. Он позволяет определять еще один тип функций.
Оператор lambda
lambda список_аргументов: выражение
В данном формате список_аргументов – это список аргументов, отделенных запятой, и выражение – значение либо любая порция программного кода, которая в результате дает значение. Например, следующие два определения функций эквивалентны:
Но в отличие от стандартной функции, после определения лямбда-функции ее можно сразу же применить, к примеру, в интерактивном режиме:
Либо, что более интересно, присвоить ее переменной, передать в другую функцию, вернуть из функции, разместить в качестве элемента последовательности или применить в программе, как обычную функцию. Приведенный ниже интерактивный сеанс это отчасти демонстрирует. (Для удобства добавлены номера строк.)
Здесь в строке 1 определяется лямбда-функция и присваивается переменной, которая теперь ссылается на лямбда-функцию. В строке 2 она применяется с двумя аргументами. В строке 4 ссылка на эту функцию присваивается еще одной переменной, и затем пользуясь этой переменной данная функция вызывается еще раз. В строке 7 создается словарь, в котором в качестве значения задана ссылка на эту функцию, и затем, обратившись к этому значению по ключу, эта функция применяется в третий раз.
Нередко во время написания программы появляется необходимость преобразовать некую последовательность в другую. Для этих целей в Python имеется встроенная функция map.
Функция map
При написании программы очень часто возникает задача, которая состоит в том, чтобы применить специальную функцию для всех элементов в последовательности. В функциональном программировании она называется отображением от англ. map.
Встроенная в Python функция map – это функция более высокого порядка, которая предназначена для выполнения именно такой задачи. Она позволяет обрабатывать одну или несколько последовательностей с использованием заданной функции. Вот общий формат функции map :
В данном формате функция – это ссылка на стандартную функцию либо лямбда-функция, и последовательности – это одна или несколько отделенных запятыми итерируемых последовательностей, т.е. списки, кортежи, диапазоны или строковые данные.
В приведенном выше интерактивном сеансе в строках 1 и 2 двум переменным, seq и seq2, присваиваются две итерируемые последовательности. В строке 3 переменной result присваивается результат применения функции map, в которую в качестве аргументов были переданы ранее определенная лямбда-функция и две последовательности. Обратите внимание, что функция map возвращает объект-последовательность map, о чем говорит строка 5. Особенность объекта-последовательности map состоит в том он может предоставлять свои элементы, только когда они требуются, используя ленивые вычисления. Ленивые вычисления – это стратегия вычисления, согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат. Программистам часто приходится обрабатывать последовательности, состоящие из десятков тысяч и даже миллионов элементов. Хранить их в оперативной памяти, когда в определенный момент нужен всего один элемент, не имеет никакого смысла. Ленивые вычисления позволяют генерировать ленивые последовательности, которые при обращении к ним предоставляют следующий элемент последовательности. Чтобы показать ленивую последовательность, в данном случае результат работы примера, необходимо эту последовательность «вычислить». В строке 6 объект map вычисляется во время преобразования в список.
Функция filter
В данном формате предикативная_функция – это ссылка на стандартную функцию либо лямбда-функция, которая возвращает истину либо ложь, и последовательность – это итерируемая последовательность, т.е. список, кортеж, диапазон или строковые данные.
Например, ниже приведена однострочная функция is_even для определения четности числа:
Чтобы отфильтровать все числа последовательности и оставить только четные, применим функцию filter :
Приведенный выше фрагмент кода можно переписать по-другому, поместив лямбда функцию в качестве первого аргумента:
Функция reduce
Наконец, когда требуется обработать список значений таким образом, чтобы свести процесс к единственному результату, для этого используется функция reduce. Функция reduce имеется в модуле functools стандартной библиотеки, но здесь она будет приведена целиком, чтобы показать, как она работает:
Вот общий формат функции reduce :
reduce(функция, последовательность, инициализатор)
В данном формате функция – это ссылка на редуцирующую функцию; ею может быть стандартная функция либо лямбда-функция, последовательность – это итерируемая последовательность, т.е. список, кортеж, диапазон или строковые данные, и инициализатор – это параметрическая переменная, которая получает начальное значение для накопителя. Начальным значением может быть значение любого примитивного типа данных либо мутабельный объект – список, кортеж и т.д. Начальное значение инициирует накапливающую переменную, которая прежде чем она будет возвращена, будет обновляться редуцирующей функцией по каждому элементу в списке.
Переданная при вызове функция вызывается в цикле для каждого элемента последовательности. Например, функция reduce может применяться для суммирования числовых значений в списке. Например, вот так:
Вот еще один пример. Если sentences – это список предложений, и требуется подсчитать общее количество слов в этих предложениях, то можно написать, как показано в приведенном ниже интерактивном сеансе:
В чем преимущества функций более высокого порядка?
Они нередко состоят из одной строки.
Все важные компоненты итерации – объект-последовательность, операция и возвращаемое значение – находятся в одном месте.
Программный код в обычном цикле может повлиять на переменные, определенные перед ним, или которые следуют после него. По определению эти функции не имеют побочных эффектов.
Приведем еще пару полезных функций.
Функция zip
Встроенная функция zip объединяет отдельные элементы из каждой последовательности в кортежи, т.е. она возвращает итерируемую последовательность, состоящую из кортежей. Вот общий формат функции zip :
В данном формате последовательность – это итерируемая последовательность, т.е. список, кортеж, диапазон или строковые данные. Функция zip возвращает ленивый объект-последовательность, который нужно вычислить, чтобы увидеть результат. Приведенный ниже интерактивный сеанс это демонстрирует:
В сочетании с оператором * эта функция используется для распаковки объединенной последовательности (в виде пар, троек и т.д.) в отдельные кортежи. Приведенный ниже интерактивный сеанс это демонстрирует:
Функция enumerate
Встроенная функция enumerate возвращает индекс элемента и сам элемент последовательности в качестве кортежа. Вот общий формат функции enumerate:
В данном формате последовательность – это итерируемая последовательность, т.е. список, кортеж, диапазон или строковые данные. Функция enumerate возвращает ленивый объект-последовательность, который нужно вычислить, чтобы увидеть результат.
Например, в приведенном ниже интерактивном сеансе показано применение этой функции к списку букв. В результате ее выполнения будет получена ленивая последовательность со списком кортежей, где каждый кортеж представляет собой индекс и значение буквы.
Функция convert в строке 1 переводит строковое значение второго элемента кортежа в верхний регистр и присоединяет к нему преобразованное в строковый тип значение первого элемента. Здесь tup – это кортеж, в котором tup[0] – это индекс элемента, и tup[1] – строковое значение элемента.
Включение в последовательность
Операции отображения и фильтрации встречаются так часто, что во многих языках программирования предлагаются способы написания этих выражений в более простых формах. Например, в языке Python возвести список чисел в квадрат можно следующим образом:
Python поддерживает концепцию под названием «включение в последовательность» (от англ. comprehension, в информатике эта операция так же называется описанием последовательности), которая суть изящный способ преобразования одной последовательности в другую. Во время этого процесса элементы могут быть условно включены и преобразованы заданной функцией. Вот один из вариантов общего формата операции включения в список:
[выражение for переменная in список if выражение2]
В данном общем формате выражение – это выражение или функция с участием переменной, которые возвращают значение, переменная – это элемент последовательности, список – это обрабатываемый список, и выражение2 – это логическое выражение или предикативная функция с участием переменной. Чтобы все стало понятно, приведем простой пример возведения список в квадрат без условия:
Приведенное выше включение в список эквивалентно следующему ниже фрагменту программного кода:
Такая форма записи называется синтаксическим сахаром, т.е. добавленная синтаксическая конструкция, позволяющая записывать выражения в более простых и кратких формах. Неплохой аспект конструкций включения в последовательность состоит еще и в том, что они легко читаются на обычном языке, благодаря чему программный код становится чрезвычайно понятным.
В конструкции включения в последовательность используется математическая запись построения последовательности. Такая запись в теории множеств и логике называется определением интенсионала множества и описывает множество путем определения условия, которое должно выполняться для всех его членов. В сущности, в терминах этих областей науки, выполняя данную операцию в Python, мы «описываем интенсионал» соответственно списка, словаря, множества и итерируемой последовательности. Ниже приведены примеры описания интенсионала соответственно списка, словаря, множества и итерируемой последовательности.
Таблица 1. Формы описания интенсионала
Выражение
Описание
[x*x for x in numbers]
set(x*x for x in numbers)
(x*x for x in numbers)
Описание последовательности. Такая форма записи создает генератор последовательности. Генератор – это объект, который можно последовательно обойти (обычно при помощи инструкции for ), но чьи значения предоставляются только тогда, когда они требуются, используя ленивое вычисление.
Отметим, что приведенные в таблице выражения (за исключением описания словаря) отличаются только ограничивающими символами: квадратные скобки применяются для описания списка, фигурные скобки – для описания словаря или множества и круглые скобки – для описания итерируемой последовательности.
Таким образом, примеры из разделов о функциях map и filter легко можно переписать с использованием включения в последовательность. Например, в строке 3 приведенного ниже интерактивного сеанса вместо функции map применена операция включения в список:
Включение в список применено и в приведенном ниже примере вместо функции filter :
Квадратные скобки в определении сигнализируют, что в результате этой операции будет создан список. Какой способ обработки последовательностей применять – с использованием функций более высокого порядка или включений, зачастую является предметом личных предпочтений.
Замыкание
Функции более высокого порядка не только получают функции на входе, но и могут порождать новые функции на выходе. Они даже в состоянии запоминать ссылку на значение в функции, которую они генерируют. Это называется замыканием. Функция, имеющая замыкание, может «запоминать» и получать доступ к среде вложенных в нее значений.
Используя замыкания, можно разделить исполнение функции со многими аргументами на большее количество шагов. Эта операция называется каррированием и обязана своим названием Хаскелю Каррингу. Каррирование – это преобразование функции многих аргументов в функцию, берущую свои аргументы по одному. Например, предположим, ваш программный код имеет приведенную ниже стандартную функцию adder :
Чтобы сделать ее каррированной, она должна быть переписана следующим образом:
Это же самое можно выразить при помощи лямбда-функций:
Обратите внимание, что в последнем примере используются две вложенные лямбда-функции, каждая из которых принимает всего один аргумент. В такой записи функция adder теперь может вызываться всего с одним аргументом. Выражение adder(3) возвращает не число, а новую, каррированную функцию. Во время вызова функции adder со значением 3 в качестве первого аргумента ссылка на значение 3 запоминается в каррированной функции. А дальше происходит следующее:
Замыкания также используются для генерирования набора связанных функций по шаблону. Использование шаблона функции помогает делать программный код более читаемым и избегать дублирования. Давайте посмотрим на приведенный ниже пример:
Функция power_generator может применяться для генерации разных функций, которые вычисляют степень:
Замыкания могут также использоваться для управления внутренним состоянием функции. Давайте предположим, что требуется функция, которая накапливает сумму всех чисел, которые ей предоставляются. Один из способов это сделать состоит в использовании глобальной переменной:
Как мы убедились, применение глобальных переменных следует избегать, потому что они загрязняют пространство имен программы. Более чистый подход состоит в использовании замыкания, чтобы включить ссылку на накапливающую переменную:
Некоторые языки программирования строго функциональны; весь код эквивалентен чистым математическим функциям. Эти языки заходят настолько далеко, что являются вневременными, причем порядок операторов в программном коде не вмешивается в поведение кода. В этих языках все присвоенные переменным значения являются немутируемыми. Такое присваивание называется однократным. Поскольку состояние программы отсутствует, то и нет момента времени, когда переменная может измениться. Вычисления в строгой функциональной парадигме просто сводятся к вычислению функций и сопоставлению с шаблонами.
Рекомендации по ФП на языке Python
Понятие ФП несколько различается по строгости формулировки. Одни понимают применение только функций, немутируемость и наведение мостов с периферией (вводом-выводом). Другие определяют ФП строже и наряду с немутируемостью говорят о применении только чистых функций. Но в любом случае программирование в функциональном стиле не тождественно функциональному программированию. Применение первоклассных функций, лямбд, итераторов, включений, каррирования и сопоставления с шаблонами вовсе не означает немутируемость и чистые функции.
Программирование в функциональном стиле не тождественно функциональному программированию.
Что делает функции нечистыми?
Глобальные мутации, т.е. внесение изменений в глобальное состояние,
Недетерминированность функций, т.е. которые для одинаковых входных значений могут возвращать разные результаты, и
Пример глобальной мутации:
Пример операции ввода-вывода:
Из чистых функций вытекает ссылочная (референциальная) прозрачность. Говорят, что программа или математическое выражение ссылочно прозрачны, если любое подвыражение можно заменить его значением, и это не приведет к изменению значения целого, т. е. скрытые побочные эффекты отсутствуют. Математические рассуждения, преобразования и доказательства корректности могут быть справедливыми только для выражений, обладающих этим свойством. А программы, написанные на обычных императивных языках, не являются ссылочно прозрачными, так как присваивание значений глобальным переменным, в некоторых случаях и локальным, вызывает скрытые побочные эффекты.
Ссылочная прозрачность (1) улучшает тестопригодность программ, т.е. поведение подпрограмм не зависит от контекста, повторный запуск приложения дает одинаковый результаты как следствие отсутствия мутаций, (2) обеспечивается модульность, т.е. поведение функций не зависит от контекста, и чистые функции можно легко составлять в композиции, строя новые формы поведений, (3) упрощает обеспечение конкурентности из-за отсутствия необходимости в синхронизации, т.к. отсутствие совместных мутируемых данных делает синхронизацию ненужной.
Однако, ФП имеет свои недостатки, такие как новизна парадигмы и иногда ухудшение производительности программ. Но в нашем случае главный недостаток состоит в том, что язык Python, как таковой, не является языком функционального программирования. Например, в нем нет библиотеки по работе с неизменяемыми структурами данных и оптимизации стека под хвостовую рекурсию. Однако эффективное функциональное программирование на Python вполне возможно.
Эффективное функциональное программирование на Python вполне возможно.
В отличие от объектно-ориентированного программирования, которое строит сложные формы поведения с помощью наследования, ФП опирается на композицию функций. Этот принцип перекликается с философией Unix, состоящей из 2 правил:
Указанные выше два простых правила делают ненужными архитектурные шаблоны и принципы ООП, заменяя их функциями! А что, спросите вы, и классы тоже? В Python использование классов не противоречит ФП, если в них отсутствует мутирующие интерфейсы.
Пример класса с мутирующим интерфейсом:
Пример класса без мутирующего интерфейса:
Но лучше использовать замороженные dataclasses и копирование, где необходимо. Иными словами, все классы должны быть замороженными dataclasses.
При всем при этом dataclasses могут быть вполне себе умными!
Также следует использовать сторонние функциональные библиотеки (например, toolz), которые обеспечивают более оптимальную композиционность функций.
Выводы
Функциональное программирование сконцентрировано вокруг немутируемости и чистых функций. Чистота позволяет производить код, который более пригоден для тестирования, функциональных композиций и управления в конкурентной обстановке. Следует избегать мутирующих интерфейсов и стремиться использовать замороженные dataclasses, сторонние библиотеки наподобие toolz и включения, при этом оставаясь идиоматичным.
Данный пост служит дополнением к моему предыдущему посту о конвейере данных. Приведенный выше материал был опубликован в качестве авторского в переводе книги Starting Out with Python и дополнен материалами Энтони Хвона.