May 11, 2024

Немного про лямбда-функции в Python

Что это такое?


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

Для их объявления нам не нужно следовать классической схеме - указывать литерал def и имя будущей функции. Нам достаточно следовать такой конструкции:

lambda arguments: expression

Это и есть весь синтаксис лямбда-функций.

Главные моменты:

  • Аргументов может быть несколько
  • Выражение может быть только одно
  • Выражение должно быть написано в одну строку
  • Лямбда-функция всегда возвращает значение

Как и когда использовать?


Чаще всего, лямбда-функции используют в нескольких случаях:

  • Как аргумент в других функциях (часто в функциях высшего порядка)
  • Для создания замыканий

Рассмотрим чуть подробнее.

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

Очень удобно применять лямбда-функции при работе с map() и filter().

Допустим, нам нужно преобразовать список. Здесь нам поможет функция map(), которая применит лямбда-функцию ко всем элементам списка:

list_1 = ["one", "two", "three"]
new_list = list(map(lambda x: "new_" + x, list_1))

print(new_list)
>>> ['new_one', 'new_two', 'new_three']

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

Эту же операцию можно решить и с помощью list comprehension (списочных выражений):

new_list = ["new_" + x for x in list_1]

Теперь рассмотрим простой пример с фильтрацией:

list_1 = ["one", "two", "three"]
new_list = list(filter(lambda x: len(x) <= 3, list_1))

print(new_list)
>>> ['one', 'two']

Мы применили функцию filter() и получили новый список с элементами, у которых количество символов меньше или равно 3.

Эту операцию тоже можно решить с помощью list comprehension:

new_list = [x for x in list_1 if len(x) <= 3]

Рассмотрим еще 1 применение при сортировке по ключу. Он будет интереснее, так как будем работать с объектами посложнее:

# Создадаем класс с атрибутами экземпляра: имя, возраст и порода
class Cat:
    def __init__(self, name: str, age: int, breed: str) -> None:
        self.name = name
        self.age = age
        self.breed = breed

    # Переопределяем магический метод __repr__, чтобы при печати экземпляров выводился сразу человекочитаемый текст
    def __repr__(self) -> str:
        return f"<<Cat {self.name}, {self.breed}, {self.age} years old>>"

# Создаем экземпляры класса Cat
cat_1 = Cat(name="Mars", age=7, breed="British Shorthair")
cat_2 = Cat(name="Oleg", age=1, breed="Devon Rex")
cat_3 = Cat(name="Bublik", age=2, breed="Russian Blue")

# Создаем неотсортированный список с питомцами
list_of_cats = [cat_3, cat_1, cat_2]

# Сортируем список по имени, возрасту и породе
name_sorted = sorted(list_of_cats, key=lambda x: x.name)
age_sorted = sorted(list_of_cats, key=lambda x: x.age)
breed_sorted = sorted(list_of_cats, key=lambda x: x.breed)

print(name_sorted, age_sorted, breed_sorted, sep="\n")

[<<Cat Bublik, Russian Blue, 2 years old>>, <<Cat Mars, British Shorthair, 7 years old>>, <<Cat Oleg, Devon Rex, 1 years old>>]
[<<Cat Oleg, Devon Rex, 1 years old>>, <<Cat Bublik, Russian Blue, 2 years old>>, <<Cat Mars, British Shorthair, 7 years old>>]
[<<Cat Mars, British Shorthair, 7 years old>>, <<Cat Oleg, Devon Rex, 1 years old>>, <<Cat Bublik, Russian Blue, 2 years old>>]

Выглядит просто, читаемо и эффективно.

Идем дальше.

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

Для начала пара слов о замыканиях.

Замыкание — это вложенная функция, которая имеет доступ к переменным внешней функции даже после закрытия этой самой внешней функции.

С помощью замыканий можно сохранять значения и состояния между вызовами функций.

Может звучать немного запутанно, но пока не будем вдаваться в детальное объяснение и рассмотрим пример. Более подробно обсудим замыкания в будущих статьях. Ну а после примера, в любом случае, станет понятнее.

def multiplier(x):
    return lambda y: x * y

multiply_by_2 = multiplier(x=2)
multiply_by_5 = multiplier(x=5)

print(multiply_by_2(y=2))
print(multiply_by_5(y=2))

>>> 4
>>> 10

Что происходит:

  • Мы объявили функцию multiplier с параметром x, внутри которой возвращается вложенная лямбда-функция, которая умножает x на y.
  • Далее мы по сути делаем из переменных multiply_by_2 и multiply_by_5 функции-перемножители, которые запоминают значение переменной x, которое будет доступно нам всегда.
  • Затем вызываем уже внутреннюю лямбда-функцию, передавая перемножителю параметр y.
  • В результате мы получаем произведение двух чисел - значений переменных x и y.

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

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

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