Что такое лямбда функция

Обновлено: 07.07.2024

Буквально на днях случайно наткнулся на Хабре на статью о лямбда-выражениях из нового (будущего) стандарта C++. Статья хорошая и даёт понять преимущества лямбда-выражений, однако, мне показалось, что статья недостаточно полная, поэтому я решил попробовать более детально изложить материал.

Вспомним основы

В прошлой статье лямбда-выражения сравнивали с указателями на функции и с функторами. Так вот первое, что следует уяснить: лямбда-выражения в C++ — это краткая форма записи анонимных функторов. Рассмотрим пример:

Фактически данный код целиком соответствует такому:

Вывод соответственно будет следующим:

На что здесь стоит обратить внимание. Во-первых, из Листинга 1 мы видим, что лямбда-выражение всегда начинается с [] (скобки могут быть непустыми — об этом позже), затем идет необязательный список параметров, а затем непосредственно тело функции. Во-вторых, тип возвращаемого значения мы не указывали, и по умолчанию лямбда возвращает void (далее мы увидим, как и зачем можно указать возвращаемый тип явно). В-третьих, как видно по Листингу 2, по умолчанию генерируется константный метод (к этому тоже еще вернемся).

Не знаю, как вам, но мне for_each, записанный с помощью лямбда-выражения, нравится гораздо больше. Попробуем написать немного усложненный пример:

В данном случае лямбда играет роль унарного предиката, то есть тип возвращаемого значения bool, хотя мы нигде этого не указывали. При наличии одного return в лямбда-выражении, компилятор вычисляет тип возвращаемого значения самостоятельно. Если же в лямбда-выражении присутствует if или switch (или другие сложные конструкции), как в приведенном ниже коде, то на компилятор полагаться уже нельзя:

Код из Листинга 4 не компилируется, а, к примеру, Visual Studio пишет ошибку на каждый return такого содержания:

Компилятор не может самостоятельно вычислить тип возвращаемого значения, поэтому мы должны его указать явно:

Теперь компиляция проходит успешно, а вывод, как и ожидалось, будет следующим:

Захват переменных из внешнего контекста

Все лямбда-выражения, приведенные выше, выглядели как анонимные функции, потому что не хранили никакого промежуточного состояния. Но лямбда-выражения в C++ — это анонимные функторы, а значит состояние они хранить могут! Используя лямбда-выражения, напишем программу, которая выводит количество чисел, попадающих в заданный пользователем интервал [lower; upper):

Ранее я упоминал, что список параметров лямбды можно опускать, когда он пустой, однако для того чтобы компилятор правильно распарсил применение слова mutable, мы должны явно указать пустой список параметров.
При выполнении программы из Листинга 8 получаем следующее:

Следует отметить, что синтаксис наподобие &out в данном случае не означает взятие адреса. Его следует читать скорее как SomeType & out, то есть это просто передача параметра по ссылке. Рассмотрим пример:

В этот раз вместо явного захвата переменной init, я указал режим захвата по умолчанию: [&]. Теперь когда компилятор встречает внутри тела лямбды переменную из внешнего контекста, он автоматически захватывает её по ссылке. Вот эквивалентный Листингу 9 код:

И соответственно вывод будет следующим:

Теперь вам главное не запутаться, что, где и когда передавать по ссылке. Фактически, если мы указываем [&] и не указываем mutable, то все равно сможем менять значение захваченной переменной и это отразится на локальной, потому что operator()() const подразумевает, что мы не можем менять, на что указывает ссылка, а это и так невозможно.

Если лямбда-выражение имеет вид [=] (int & _val) mutable , то переменные захватываются по значению, но меняться будет только их внутренняя копия, а вот параметр передается по ссылке, то бишь изменения отразятся и на оригинале. Если [] (const SomeBigObject & _val) , то ничего не захватывается, а параметр принимается по константной ссылке и т.д.

А что будет, если мы напишем такое, слегка надуманное лямбда-выражение внутри метода класса:

Несмотря на все наши ожидания, код не будет скомпилирован, так как компилятор не сможет захватить m_val и m_power: эти переменные вне области видимости. Вот что говорит на это Visual Studio:

Как же быть? Чтобы получить доступ к членам класса, в capture-list нужно поместить this:

Данная программа делает именно то, чего мы ожидали:

Следует заметить, что this можно захватить только по значению, и если вы попытаетесь произвести захват по ссылке, компилятор выдаст ошибку. Даже если вы в коде из Листинга 12 напишете [&] вместо [this], то this будет все равно захвачен по значению.

Прочее

Помимо всего вышеперечисленного, в заголовке лямбда-выражения можно указать throw-list — список исключений, которые лямбда может сгенерировать. Например, такая лямбда не может генерировать исключения:

А такая генерирует только bad_alloc:

Естественно, если его не указывать, то лямбда может генерировать любое исключение.

К счастью, в финальном варианте стандарта throw-спецификации объявлены устаревшими. Вместо этого оставили ключевое слово noexcept, которое говорит, что функция не должна генерировать исключение вообще.

Повторное использование лямбда-выражений. Генерация лямбда-выражений.

Все вышеперечисленное довольно удобно, но основная мощь лямбда-выражений приходится на то, что мы можем сохранить лямбду в переменной или передавать как параметр в функцию. В Boost для этого есть класс Function, который, если я не ошибаюсь, войдет в новый стандарт STL (возможно, в немного измененном виде). На данный момент уже можно поюзать фичи из обновленного STL, однако, пока что эти фичи находятся в подпространстве имен std::tr1.

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

Рассмотрим следующий пример:

Данная программа выводит:

Рассмотрим подробнее. Вначале у нас инициализируется вектор с помощью generate_n(). Тут всё просто. Далее мы создаем переменную traceLambda типа function (то есть функция, принимающая int и возвращающая void) и присваиваем ей лямбда-выражение, которое выводит на консоль значение и пробел. Далее мы используем только что сохраненную лямбду для вывода всех элементов вектора.

После этого мы видим немаленькое объявление lambdaGen, которая является лямбда-выражением, принимающим один параметр int и возвращающим другую лямбду, принимающую int и возвращающую int.

Следом за этим мы ко всем элементам вектора применяем transform(), в качестве мутационной функции для которого указываем lambdaGen(2). Фактически lambdaGen(2) возвращает другую лямбду, которая прибавляет к переданному параметру число 2 и возвращает результат. Этот код, естественно, немного надуманный, ибо то же самое можно было записать как

однако в качестве примера довольно показательно.

Затем мы снова выводим значения всех элементов вектора, используя для этого сохраненную ранее лямбду traceLambda.

Кроме того что ключевое слово auto весьма полезно при работе с циклами вида

его очень удобно использовать с лямбда-выражениями. Теперь код из Листинга 13 можно переписать так:

Пожалуй, на этом я закончу описание лямбда-выражений. Если будут вопросы, поправки или замечания, с удовольствием выслушаю.

ETA (20.02.2012): Оказалось, что для кого-то эта статья до сих пор актуальна, поэтому поправил подсветку синтаксиса и подкорректировал информацию про throw-списки в объявлении лямбд. Помимо непосредственно лямбда-выражений другие фичи из нового стандарта С++11 (например, списки инициализации контейнеров) решил не добавлять, так что статья осталась практически в первозданном виде.

image

Я решил написать эту серию статей, ибо считаю, что никто не должен сталкиваться с той стеной непонимания, с которой столкнулся когда-то я.

Ведь большинство статей написаны таки образом что, для того чтобы понять что-то в Функциональном Программировании (далее ФП), тебе надо уже знать многое в ФП. Эту статью я старался написать максимально просто — настолько понятно, чтобы её суть мог уловить мой племянник, школьник, который сейчас делает свои первые шаги в 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 выражения, и сможете применять их в работе.

Но где же тут та самая экономия места, времени и памяти? Экономится максимум пара строк.

image

Конечно многие из нас знакомы с этим понятием, однако данная статья рассчитана на новичков. В данном посте постараюсь рассмотреть данный феномен и привести примеры использования. Для начала необходимо понять что же такое лямбда-функция. Итак, лямбда-функция, часто ее называют анонимной, т. е. функция при определении которой не нужно указывать ее имя. Возвращаемое значение такой функцией присваивается переменной, через которую в последствие эту функцию можно вызывать.
До выхода PHP 5.3 определять лямбда-функции было возможно, но их нельзя было назвать полноценными. Сейчас я приведу пару примеров и продолжим рассматривать данные понятия.

Конечно динамическое создание функций не решает всех проблем, однако порой написание такой одноразовой функции может быть полезным. Можно расширить наш пример:

Понятие замыкания наверняка знакомо программистам на JavaScript, а так же программистам на многих других языках. Замыкание — это функция, охватывающая или замыкающая текущую область видимости. Что бы понять все это, рассмотрим пример:

Как вы уже могли заметить, функция не имеет имени и результат присваивается переменной. Лямбда-функция, созданная таким образом, возвращает значение в виде объекта типа closure.
В PHP 5.3 стало возможно вызывать объекты как если бы они были функциями. А именно магический метод __invoke() вызывается каждый раз, когда класс вызывается как функция.
Переменные недоступны внутри функции, если они не объявлены глобальными, так же переменные из дочернего контекста недоступны если только не используется зарезервированное слово use. Обычно в PHP переменные передаются в замыкание значением, это поведение можно изменить с помощью ампермсанда перед переменной в выражении use. Рассмотрим пример:

Если убрать амперсанды то оба раза выведется 80, т. к. переменная $mul внутри замыкания будет копией, а не ссылкой.
Итак, осталось только выяснить как это можно применить на практике.
Рассмотрим пример:

Этот пример уже можно использовать для достаточно гибкого прототипирования. Достаточно объявить методы для всех SQL-операций с объектом.
Автор не призывает всех придерживаться такой практики, равно как и не считает что так лучше, все вышеописанное лишь пример использования, причем возможно не самый техничный и интересный, и не более того.
UPD Говоря о том самом длинном регулярном выражении, я не стал подписывать его в комментариях и решил вынести сюда. Оно лишь ищет строки в одинарных и двойных кавычках, а так же имена таблиц и экранирует их.

В этой статье вы узнаете о том, что такое лямбда-функции в Python. На самом деле, если вы знаете, что такое функции и умеете с ними работать, то знаете и что такое лямбда.

Лямбда-функция в Python — это просто функция Python. Но это некий особенный тип с ограниченными возможностями. Если есть желание погрузиться глубже и узнать больше, то эта статья целиком посвящена lambda .

Что такое лямбда в Python?

Прежде чем переходить как разбору понятия лямбда в Python, попробуем понять, чем является обычная функция Python на более глубоком уровне.

Для этого потребуется немного поменять направление мышление. Как вы знаете, все в Python является объектом.

Например, когда мы запускаем эту простейшую строку кода

Создается объект Python типа int , который сохраняет значение 5. x же является символом, который ссылается на объект.

Теперь проверим тип x и адрес, на которой он ссылается. Это можно сделать с помощью встроенных функций type и id .

В итоге x ссылается на объект типа int , а расположен он по адресу, который вернула функция id .

Просто и понятно.

А что происходит при определении вот такой функции:

Повторим упражнение и узнаем type и id объекта f .

Оказывается, в Python есть класс function , а только что определенная функция f — это его экземпляр. Так же как x был экземпляром класса integer . Другими словами, о функциях можно думать как о переменных. Разница лишь в том, что переменные хранят данные, а функции — код.

Это же значит, что функции можно передать в качестве аргументов другим функциям или даже использовать их как тип возвращаемого значения.

Рассмотрим простой пример, где функция f передается другой функции.

Попробуйте разобраться самостоятельно с тем, что делает этот код, прежде чем читать дальше.

Итак, modify_list — это функция, которая принимает список L и функцию fn в качестве аргументов. Затем она перебирает список элемент за элементом и применяет функцию к каждому из них.

Это общий способ изменения объектов списка, ведь он позволяет передать функцию, которая займется преобразованием. Так, если передать modify_list функцию f , то результатом станет список, где все значения будут возведены в квадрат.

Но можно передать и любую другую, которая изменит оригинальный список другим способом. Это очень мощный инструмент.

Теперь, когда с основами разобрались, стоит перейти к лямбда. Лямбда в Python — это просто еще один способ определения функции. Вот базовый синтаксис лямбда-функции в Python:

Лямбда принимает любое количество аргументов (или ни одного), но состоит из одного выражения. Возвращаемое значение — значение, которому присвоена функция. Например, если нужно определить функцию f из примера выше, то это можно сделать вот так:

Но возникает вопрос: а зачем нужны лямбда-функции, если их можно объявлять традиционным образом? Но на самом деле, они полезны лишь в том случае, когда нужна одноразовая функция. Такие функции еще называют анонимными. И, как вы увидите дальше, есть масса ситуаций, где они оказываются нужны.

Лямбда с несколькими аргументами

Определить лямбда-функцию с одним аргументом не составляет труда.

А если их должно быть несколько, то достаточно лишь разделить значения запятыми. Предположим, что нужна функция, которая берет два числовых аргумента и возвращает их произведение.

Отлично! А как насчет лямбда-функции без аргументов?

Лямбда-функция без аргументов

Допустим, нужно создать функцию без аргументов, которая бы возвращала True . Этого можно добиться с помощью следующего кода.

Несколько лямбда-функций

В определенный момент возникнет вопрос: а можно ли иметь лямбда-функцию из нескольких строк.

Ответ однозначен: нет.

Лямбда-функции в Python всегда принимают только одно выражение. Если же их несколько, то лучше создать обычную функцию.

Примеры лямбда-функций

Теперь рассмотрим самые распространенные примеры использования лямбда-функций.

Лямбда-функция и map

Распространенная операция со списками в Python — применение операции к каждому элементу.

map() — это встроенная функция Python, принимающая в качестве аргумента функцию и последовательность. Она работает так, что применяет переданную функцию к каждому элементу.

Предположим, есть список целых чисел, которые нужно возвести в квадрат с помощью map .

Обратите внимание на то, что в Python3 функция map возвращает объект Map , а в Python2 — список.

Так, вместо определения функции и передачи ее в map в качестве аргумента, можно просто использовать лямбда для быстрого определения ее прямо внутри. В этом есть смысл, если упомянутая функция больше не будет использоваться в коде.

Вот еще один пример.

Лямбда-функция и filter

filter() — это еще одна встроенная функция, которая фильтрует последовательность итерируемого объекта.

Другими словами, функция filter отфильтровывает некоторые элементы итерируемого объекта (например, списка) на основе какого-то критерия. Критерий определяется за счет передачи функции в качестве аргумента. Она же применяется к каждому элементу объекта.

Если возвращаемое значение — True , элемент остается. В противном случае — отклоняется. Определим, например, простую функцию, которая возвращает True для четных чисел и False — для нечетных:

С лямбда-функциями это все можно сделать максимально сжато. Код выше можно преобразовать в такой, написанный в одну строку.

И в этом сила лямбда-функций.

Лямбда-функция и сортировка списков

Сортировка списка — базовая операция в Python. Если речь идет о списке чисел или строк, то процесс максимально простой. Подойдут встроенные функции sort и sorted .

Но иногда имеется список кастомных объектов, сортировать которые нужно на основе значений одного из полей. В таком случае можно передать параметр key в sort или sorted . Он и будет являться функцией.

Функция применяется ко всем элементам объекта, а возвращаемое значение — то, на основе чего выполнится сортировка. Рассмотрим пример. Есть класс Employee .

Теперь создадим экземпляры этого класса и добавим их в список.

Предположим, что мы хотим отсортировать его на основе поля age сотрудников. Вот что нужно сделать для этого:

Лямбда-выражение было использовано в качестве параметра key вместо отдельного ее определения и затем передачи в функцию sort .

Пара слов о выражениях и инструкциях

Как уже упоминалось, лямбда могут иметь только одно выражение (expression) в теле.

Обратите внимание, что речь идет не об инструкции (statement).

Выражение и инструкции — две разные вещи, в которых часто путаются. В программировании инструкцией является строка кода, выполняющая что-то, но не генерирующая значение.

Например, инструкция if или циклы for и while являются примерами инструкций. Заменить инструкцию на значение попросту невозможно.

А вот выражения — это значения. Запросто можно заменить все выражения в программе на значения, и программа продолжит работать корректно.

  • 3 + 5 — выражение со значением 8
  • 10 > 5 — выражение со значением True
  • True and (5 < 3) — выражение со значением False

Тело лямбда-функции должно являться выражением, поскольку его значение будет тем, что она вернет. Обязательно запомните это для работы с лямбда-функциями в будущем.

Smartiqa Failed Testsuits

Всему вокруг нас присвоено свое название. Названия помогают нам запоминать окружающий мир путем построения ассоциаций. Мир программирования - не исключение. Но обязательно ли это? Или мы можем оставить какой-то объект без собственного имени? Да, и Python позволяет нам создавать лямбда функции, также известные как анонимные функции.

В этой статье я расскажу вам об этой прекрасной возможности языка Python в следующем порядке:

  1. Что такое лямбда функции?
  2. Зачем нужны лямбда функции?
  3. Как написать лямбда функцию?
  4. Как лямбда функции уменьшают код?
  5. Лямбда функции в обычных функциях
  6. Использование лямбда функций в: filter(), map(), reduce()

Что такое лямбда функции?

Зачем нужны лямбда функции?

Основная причина применения лямбда функций — создание функции, которая используется в коде единожды. Использование таких функций позволяет снизить число строк кода, которые вам придется написать. Также лямбда функции известны как throw-away функции, которые используются вместе с filter() , map() и т. п.

Как написать лямбда функцию?

Лямбда функции могут иметь сколько угодно аргументов или не иметь их вовсе, но обязательно должны содержать лишь одно выражение. В следующем примере создается лямбда функция без аргументов и при вызове возвращает строку:

В обоих примерах вы могли подметить для себя, что используется разное количество аргументов, но везде есть только одно выражение. Не стоит забывать, что лямбда функции, в отличие от обычных методов, могут содержать только те выражения, которые вы можете уместить в одну строку.

Как лямбда функции уменьшают код?

Как вы могли подметить, объявление лямбда функции априори требует меньше места (1 строка кода), чем обычной функции (2 строки кода). Давайте повторим функционал лямбда функции, которую мы написали в прошлом примере:

Как вы видите, в примере выше нам требуется написать return , чтобы получить результат работы метода. Лямбда функции, в свою очередь, не требуют табуляции и легко умещаются в одну строку.

Smartiqa лямбда функции

В любом случае, лямбда функцию необходимо присвоить какой-либо переменной, чтобы ее впоследствии вызвать.

Лямбда функции следует применять в других методах более высокого порядка, которые либо принимают в качестве аргумента функцию, либо возвращают ее.

Лямбда функции в обычных функциях

Как упоминалось ранее, лямбда функции лучше использовать в связке с обычными функциями.

Пример ниже состоит из обычной функции add() , которая принимает один аргумент a . Этот аргумент добавляется к пока что еще не известному аргументу лямбда функции, которая возвращается после исполнения add() . Кстати, лямбда функция вызывается каждый раз, когда мы вызываем add() . Это позволяет нам использовать различные значения для аргументов.

Читайте также: