Django-Admin.ru

lambda-функции в Python

Что такое lambda в Python: синтаксис анонимной функции, lambda в sorted, max, map и filter, условие внутри lambda, ловушка с циклом и когда лучше def.

Редакция Django-Admin.ru
· обновлено · 8 мин чтения

Содержание · 10 разделов
  1. Синтаксис lambda
  2. lambda и def: в чём разница
  3. lambda как ключ сортировки: sorted, min, max
  4. lambda в map, filter и reduce
  5. Условие внутри lambda
  6. Другие места, где пригодится lambda
  7. Когда lambda не нужна
  8. Уже есть готовая функция
  9. lambda, сохранённая в переменную
  10. Логика длиннее одной строки
  11. Частые ошибки
  12. return или присваивание внутри lambda
  13. Переменная цикла в lambda: все функции возвращают одно значение
  14. Забыли вызвать или передали результат вызова
  15. Запятая после выражения
  16. Частые вопросы
  17. Можно ли вызвать print внутри lambda?
  18. lambda работает быстрее, чем def?
  19. Можно ли написать рекурсивную lambda?
  20. Бывает ли асинхронная lambda?
  21. Можно ли передать lambda в default поля модели Django?
  22. Итог

lambda в Python создаёт короткую анонимную функцию из одного выражения: lambda аргументы: выражение. Результат выражения возвращается автоматически, return не пишется. Короткий ответ:

square = lambda x: x * x
print(square(5))                    # → 25
print((lambda a, b: a + b)(2, 3))   # → 5

На практике lambda почти никогда не присваивают переменной, а передают прямо в другую функцию — чаще всего как аргумент key в sorted(), min() и max() или в map() и filter(). Ниже — синтаксис, типичные сценарии, ограничения и ошибки. Как устроены обычные функции с def, читайте в статье о функциях в Python. Примеры проверены на Python 3.12.

Синтаксис lambda

lambda параметры: выражение
  • Параметров может быть сколько угодно, в том числе ноль.
  • После двоеточия — ровно одно выражение. Его значение и есть результат функции.
  • Поддерживаются те же виды параметров, что и в def: значения по умолчанию, *args, **kwargs.
no_args = lambda: "готово"
two = lambda a, b: a * b
default = lambda name, greeting="Привет": f"{greeting}, {name}!"
star = lambda *nums: sum(nums)

print(no_args())                                  # → готово
print(two(3, 4))                                  # → 12
print(default("Анна"))                            # → Привет, Анна!
print(default("Олег", greeting="Здравствуйте"))   # → Здравствуйте, Олег!
print(star(1, 2, 3))                              # → 6

lambda и def: в чём разница

lambda создаёт объект того же типа function, что и def. Различаются они синтаксисом и возможностями:

def square_def(x):
    return x * x

square = lambda x: x * x
print(square_def(4) == square(4))           # → True
print(square_def.__name__, square.__name__) # → square_def <lambda>
print(type(square))                          # → <class 'function'>
Признакdeflambda
Имяесть (square_def)<lambda>
Телолюбые инструкции, много строкодно выражение
returnявныйнеявный — результат выражения
Docstring и аннотации типовданет
Где уместнавездекороткая функция «на месте»

Из-за отсутствия имени в traceback ошибка внутри lambda видна как in <lambda> — по такой строке сложнее понять, какая именно функция упала.

lambda как ключ сортировки: sorted, min, max

Главный сценарий — параметр key. Функция-ключ получает элемент и возвращает значение, по которому элементы сравниваются:

words = ["pear", "fig", "Kiwi", "date", "apple"]

print(sorted(words, key=lambda s: len(s)))
# → ['fig', 'pear', 'Kiwi', 'date', 'apple']

print(sorted(words, key=lambda s: (len(s), s.lower())))
# → ['fig', 'date', 'Kiwi', 'pear', 'apple']

Во втором примере lambda возвращает кортеж: сначала слова сравниваются по длине, при равенстве — по алфавиту без учёта регистра.

Сортировка списка словарей и поиск максимума по полю:

users = [
    {"name": "Олег", "age": 25},
    {"name": "Анна", "age": 31},
    {"name": "Иван", "age": 19},
]

print(sorted(users, key=lambda u: u["age"])[0]["name"])  # → Иван
print(max(users, key=lambda u: u["age"])["name"])        # → Анна

users.sort(key=lambda u: u["name"])   # сортировка на месте
print([u["name"] for u in users])     # → ['Анна', 'Иван', 'Олег']

Сортировка словаря по значению — тот же приём с items():

scores = {"Олег": 72, "Анна": 95, "Иван": 88}
print(sorted(scores.items(), key=lambda item: item[1], reverse=True))
# → [('Анна', 95), ('Иван', 88), ('Олег', 72)]

Больше приёмов работы со словарями — в статье о словарях Python, со списками — в статье о списках.

lambda в map, filter и reduce

from functools import reduce

nums = [1, 2, 3, 4, 5, 6]
print(list(map(lambda n: n * 10, nums)))         # → [10, 20, 30, 40, 50, 60]
print(list(filter(lambda n: n % 2 == 0, nums)))  # → [2, 4, 6]
print(reduce(lambda acc, n: acc + n, nums))      # → 21

map() и filter() возвращают ленивые итераторы, поэтому результат обёрнут в list(). Те же задачи часто короче решаются генератором списка: [n * 10 for n in nums] и [n for n in nums if n % 2 == 0]. Когда что выбирать, подробно разобрано в статье о функции map в Python.

Условие внутри lambda

Инструкцию if в lambda написать нельзя, но можно использовать условное выражение a if условие else b:

sign = lambda n: "положительное" if n > 0 else ("ноль" if n == 0 else "отрицательное")
print(sign(5), sign(0), sign(-3))  # → положительное ноль отрицательное

Если ветвлений больше двух, такую строку трудно читать — перепишите её обычной функцией с if/elif/else. Синтаксис условий и тернарного оператора описан в статье об условиях if else в Python.

Другие места, где пригодится lambda

  • Фабрика значений по умолчанию: defaultdict(lambda: "нет данных") — функцию без аргументов вызывают для каждого отсутствующего ключа.
  • Обработчики событий в GUI: в Tkinter command=lambda: save(file_name) передаёт кнопке функцию с заранее подставленным аргументом.
  • Небольшие преобразования в pandas: df["price"].apply(lambda p: p * 1.2).

Когда lambda не нужна

Уже есть готовая функция

Не оборачивайте в lambda то, что и так является функцией:

words = ["banana", "Apple", "cherry"]
sorted(words, key=lambda w: len(w))   # лишняя обёртка
sorted(words, key=len)                # то же самое
sorted(words, key=str.lower)          # метод как функция

Для доступа к ключу словаря или элементу кортежа есть operator.itemgetter, для атрибутов объекта — operator.attrgetter:

from operator import itemgetter

users = [{"name": "Олег", "age": 25}, {"name": "Анна", "age": 31}]
print([u["name"] for u in sorted(users, key=itemgetter("age"))])  # → ['Олег', 'Анна']

lambda, сохранённая в переменную

Запись square = lambda x: x * x работает, но руководство по стилю PEP 8 рекомендует в таком случае писать def: функция получит нормальное имя в traceback, её можно снабдить docstring и аннотациями. Линтеры (flake8, Ruff) помечают такое присваивание правилом E731.

Логика длиннее одной строки

Если хочется втиснуть в lambda несколько действий через вложенные тернарные операторы и and/or, это сигнал вынести код в обычную функцию.

Частые ошибки

return или присваивание внутри lambda

f = lambda x: return x
# → SyntaxError: invalid syntax

В lambda допустимо только выражение. return, if как инструкция, for, try, присваивание через = — синтаксические ошибки.

Переменная цикла в lambda: все функции возвращают одно значение

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])  # → [2, 2, 2]

lambda запоминает не значение i, а саму переменную. К моменту вызова цикл закончился и i равно 2. Зафиксируйте текущее значение через параметр по умолчанию — он вычисляется в момент создания функции:

funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])  # → [0, 1, 2]

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

Забыли вызвать или передали результат вызова

print(double) для double = lambda x: x * 2 выведет <function <lambda> at 0x...>, а не число — нужен вызов double(3). Обратная ошибка — sorted(words, key=len(w)): в key передаётся функция, а не результат её вызова.

Запятая после выражения

f = lambda x: x + 1, x + 2

Python читает эту строку как кортеж из lambda и значения x + 2. Если x не определён, получите NameError. Чтобы вернуть два значения, возьмите их в скобки: lambda x: (x + 1, x + 2).

Частые вопросы

Можно ли вызвать print внутри lambda?

Можно: print() — вызов функции, то есть выражение. Сама lambda при этом вернёт результат print(), то есть None:

log = lambda msg: print("LOG:", msg)
result = log("старт")  # → LOG: старт
print(result)          # → None

lambda работает быстрее, чем def?

Нет. Обе записи компилируются в одинаковый байт-код, скорость вызова не различается. Выбирайте по читаемости: короткая функция на месте — lambda, всё остальное — def.

Можно ли написать рекурсивную lambda?

Технически да, если присвоить lambda имени и обращаться к нему в теле: fact = lambda n: 1 if n <= 1 else n * fact(n - 1), fact(5) вернёт 120. Но такая запись нарушает рекомендацию PEP 8 о присваивании lambda, и рекурсию понятнее писать через def.

Бывает ли асинхронная lambda?

Нет, записи async lambda в Python нет — это SyntaxError. Для корутины объявите функцию через async def. lambda может вернуть уже созданную корутину, например lambda: fetch(url), если вызывающий код сам её дождётся через await.

Можно ли передать lambda в default поля модели Django?

Нельзя: миграции Django не умеют сериализовать lambda, и makemigrations завершится ошибкой. Объявите обычную функцию на уровне модуля и передайте её без скобок: default=default_tags. Про поля моделей — в статье о моделях Django.

Итог

lambda параметры: выражение — компактный способ создать одноразовую функцию прямо в месте использования: в key для sorted(), min(), max(), в map() и filter(). Если функция нужна повторно, содержит несколько шагов или заслуживает имени — пишите def. Остальной синтаксис языка собран в шпаргалке по синтаксису Python, а функции, которые принимают и возвращают функции, разобраны в статье о декораторах.

  • #Python
  • #lambda
  • #Функции
  • #sorted
  • #Синтаксис
↑ ↓ выборEnter открытьEsc закрыть