Лямбда-функции в Python: что это и как работают

Безымянные функции для быстрого использования

Лямбда-функции в Python: что это и как работают

Мы уже много рассказали про разные встроенные функции в языке Python. Сегодня расскажем не про какую-то конкретную, а про особый вид функций — lambda. Это инструмент, которым удобно пользоваться для расширения возможностей кода, когда программу нужно сделать немного более функциональной, но без сильного усложнения.

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

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

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

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

# объявляем функцию без аргументов
def classic():
   # функция выводит строку и заканчивает работу
   print('Классическая функция в Python')

# для запуска пишем имя функции и скобки
classic()

Теперь, если запустить этот код, он выведет строку:

Классическая функция в Python

Такую функцию можно создать и вызывать в любом месте по её названию.

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

Самое частое и с точки зрения хорошего python-кода правильное применение таких функций — использовать их в качестве аргумента других функций. Поэтому первый пример будет выглядеть немного сложно, зато более полезно. Чтобы было проще, дальше мы всё разберём максимально подробно.

# создаём список
numbers = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]

# применяем встроенную в Python функцию filter().
# в аргументах мы даём ей две вещи: лямбда-функцию и созданный выше список.
# filter() проверит, соответствуют ли элементы списка заданному условия после слова lambda
filtered_numbers = filter(lambda x: x > 0, numbers)

# функция filter() возвращает объект специального типа,
# поэтому сначала мы преобразуем результат в список, а потом выводим его экран
print(list(filtered_numbers))

В этом коде у нас есть список — коллекция из нескольких значений. 

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

Для фильтрации значений мы используем другую функцию — filter(). Если интересно, как она работает, про неё у нас есть отдельная статья. Мы создаём условие, чтобы получить только положительные номера из списка, — и как раз для этого условия используем lambda-функцию.

Теперь после запуска мы получим отфильтрованный список:

[1, 2, 3, 4, 5]

Синтаксис лямбда-функций

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

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

Формула lambda-функции выглядит так:

lambda arguments: expression

Здесь lambda — ключевое слово, arguments — аргументы для работы, а expression — выражение, которое с этими аргументами что-то делает и возвращает получившееся после этих вычислений значение.

Если взять пример синтаксиса без использования в реальном коде, то это выглядит так:

lambda x, y: x ** 2 + y ** 3

Эта функция принимает два аргумента. После этого она возводит первый аргумент в квадрат, а второй в куб. В конце складывает эти значения и возвращает эту сумму программе. И всё это в рамках одного выражения.

А если сделать так, чтобы эта лямбда-функция реально работала, то её нужно поместить внутри или другой функции, или переменной:

# объявляем переменную и присваиваем в неё lambda-функцию
anonymous = lambda x, y: x ** 2 + y ** 3

# вызываем lambda-функцию, передав ей в качестве аргументов два числа
print(anonymous(3, 4))

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

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

Масштабировать лямбда-функции сложно. Для объяснения сравним подробнее классические функции и безымянные.

Различие между лямбда-функциями и обычными функциями

Оба вида функций могут принимать неограниченное количество аргументов для работы, и в этом их единственное сходство.

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

Ещё такая функция может ничего не возвращать в результате работы.

Лямбда-функция ничего этого не умеет. Она принимает только одно выражение и работает с аргументами только по этому правилу. В безымянных функциях нельзя изменять переменные, которые объявлены не в ней, потому что не работает использование оператора присваивания = или :=

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

Использование лямбда-функций

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

Звучит сложно, поэтому посмотрим на примерах. Для этого возьмём три встроенные функции Python, которые могут использовать внутри себя lambda-функции. Все три работают с коллекциями — списками, кортежами, строками и другими наборами элементов, которые структурированы определённым образом. 

Элементы коллекций можно перебирать, то есть итерироваться по элементам. Во время итерации к каждому элементу можно применять прописанные в программе действия.

Лямбда-функции с функцией map()

Map() проходит по всем элементам коллекции и делает с ними то, что скажет разработчик. 

Работает это так: map() принимает два аргумента: какое-то выражение или функцию и итерируемую коллекцию. После этого применяет прописанное первым аргументом действие к каждому элементу, например можно умножить все числа в списке на 2 или привести символы в строке к нижнему регистру.

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

Этот код берёт созданный список чисел и умножает каждый элемент на 2:

# создаём список чисел
numbers = [1, 2, 3, 4]

# применяем лямбда-функцию, которая умножает каждый элемент на 2
doubled_numbers = list(map(lambda x: x * 2, numbers))

# выводим результат
print(doubled_numbers)

Если запустить эту программу, в консоли получим:

[2, 4, 6, 8]

Лямбда-функции с функцией filter()

Filter() проверяет, соответствуют ли элементы коллекции условию. Если нет — выкидывает их из последовательности. Остаются только те, что проходят проверку.

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

Вот как это выглядит:

# создаём список строк
heroes = ["Крис Адамс", "Вин Таннер", "Чико", "Бритт", "Гарри Лаки", "Ли Берн", "Lambda Python"]

# фильтруем, оставляя только имена, начинающиеся на "К"
filtered_heroes = list(filter(lambda x: x.startswith('К'), heroes))

# выводим результат
print(filtered_heroes)

При запуске мы получим только те элементы, которые начинаются на заглавную «К»:

['Крис Адамс']

Лямбда-функции с функцией reduce()

Эта функция проходит по всем элементам и тоже последовательно применяет одну инструкцию к каждому. Но при этом она сохраняет значение от каждой предыдущей операции, то есть аккумулирует его. В каждом следующем действии должны участвовать этот аккумулятор и новый элемент. В конце reduce() вернёт итоговое единичное значение. Получается, что функция как бы сворачивает всю коллекцию до одного значения.

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

Пример того, как это может работать, — сложим все элементы списка:

# импортируем reduce
from functools import reduce

# создаём список чисел
numbers = [1, 2, 3, 4]

# складываем все числа через reduce и лямбда-функцию
sum_numbers = reduce(lambda a, b: a + b, numbers)

# выводим результат
print(sum_numbers)

В результате получаем одного значение:

10

Лямбда-функции и списковые включения

Списковые включения, генераторы списков или list comprehensions — это всё один и тот же инструмент для создания списков по нужному нам правилу. Один из вариантов создания правила — использовать лямбда-функцию и записать в ней инструкцию по созданию элементов списка.

Для примера сделаем так: создадим генератор, который возьмёт числа от 1 до 10 и добавит в список только те, что кратны 3:

# генерируем числа от 1 до 10 и оставляем только те, что делятся на 3
result = [x for x in range(1, 11) if (lambda x: x % 3 == 0)(x)]

# выводим результат на экран
print(result)

В терминале запуска получаем:

[3, 6, 9]

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

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

Например, нам нужно оставить в списке только строки длиннее 4 символов. Мы можем сделать это с явным использованием if и else, тогда это будет выглядеть так:

# создаём список городов
cities = ["Москва", "Сочи", "Нью-Йорк", "Токио", "Париж", "Баку", "Рим"]

# проверяем каждый элемент на длину и возвращаем 
# значение True, если длина больше 4. функция filter() 
# оставляет только те значения, которые вернут True
filtered_cities = list(filter(lambda city: True if len(city) > 4 else False, cities))

# выводим результат на экран
print(filtered_cities)

Но такое условие получается довольно громоздким и не сразу понятным. Проще сделать это через сравнение >:

# создаём список городов
cities = ["Москва", "Сочи", "Нью-Йорк", "Токио", "Париж", "Баку", "Рим"]

# оставляем только города с длиной названия > 4 символов
filtered_cities = list(filter(lambda city: len(city) > 4, cities))

# выводим результат на экран
print(filtered_cities)

Оба варианта выведут один результат:

['Москва', 'Нью-Йорк', 'Токио', 'Париж']

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

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

Например, такая функция не будет работать:

# пытаемся поместить в одну функцию два выражения
function = lambda x: (print(x); x + 1)

Python позволяет добавлять выражения в одну строку, но в безымянных функциях это не предусмотрено, поэтому при запуске мы увидим такое сообщение в консоли:

f = lambda x: (print(x); x + 1)
^
SyntaxError: invalid syntax

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

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

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

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

Пример с условием

У нас есть список зарегистрированных температур в градусах по Цельсию. Все показатели нужно перевести в градусы по Фаренгейту и добавить в новый список только те, что выше 20 градусов.

Если использовать операторы if и else, получается такой код:

# список температур в градусах Цельсия
temperatures = [25, 30, 15, 10, 35]

# преобразуем в градусы Фаренгейта и оставляем только значение выше 20 градусов
hot_temps_fahrenheit = list(
   map(
       # преобразуем в градусы Фаренгейта
       lambda x: (x * 9/5) + 32,
       filter(
           # фильтруем значения ниже 20 градусов
           lambda x: True if x > 20 else False, 
           temperatures
       )
   )
)

# выводим на экран
print(hot_temps_fahrenheit)

Здесь сначала мы преобразуем все значения, проходя по списку функцией map(). После этого оставляем только показатели выше 20 градусов через filter().

То же самое можно сделать без применения if и else. Получится небольшой алгоритм:

# список температур в градусах Цельсия
temperatures = [25, 30, 15, 10, 35]

# преобразуем в градусы Фаренгейта и оставляем только значение выше 20 градусов
hot_temps_fahrenheit = list(
   # преобразуем в градусы Фаренгейта
   map(lambda x: (x * 9/5) + 32,
   # оставляем только значения выше 20
   filter(lambda x: x > 20, temperatures))
)

# выводим на экран
print(hot_temps_fahrenheit)

И первый, и второй вариант вернут там такой список:

[77.0, 86.0, 95.0]

Пример с множественными операторами

Если всё-таки нужно добавить несколько операций в лямбда-функцию, это можно сделать при помощи кортежа.

В примере ниже мы проходим по списку имён функцией map(). Для каждого имени сначала преобразуем первую букву в заглавную, а потом добавляем в кортеж длину строки. 

Получаем такую программу:

# список имён
names = ["Алиса", "Пётр", "Вячеслав"]

# преобразуем: первая буква заглавная + длина имени
use = list(map(lambda x: (x.capitalize(), len(x)), names))

# выводим на экран
print(use)

Код выводит такой результат:

[('Алиса', 5), ('Пётр', 4), ('Вячеслав', 8)]

Преимущества и недостатки лямбда-функций

После нескольких примеров видно, что лямбда-функция — это анонимный и упрощённый аналог обычной функции. Её можно вставить там, где достаточно одного выражения и если это выражение будет выполнять единственную задачу.

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

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

Поэтому использовать их нужно осторожно, чтобы не усложнять работу.

Бонус для читателей

Если вам интересно погрузиться в мир ИТ и при этом немного сэкономить, держите наш промокод на курсы Практикума. Он даст вам скидку при оплате, поможет с льготной ипотекой и даст безлимит на маркетплейсах. Ладно, окей, это просто скидка, без остального, но хорошая. 

Вам слово

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

Обложка:

Алексей Сухов

Корректор:

Александр Зубов

Вёрстка:

Егор Степанов

Соцсети:

Юлия Зубарева

Вам может быть интересно
hard