Функция без имени: лямбда
Представьте, что вам нужно отсортировать список слов не по алфавиту, а по длине. Для этого sorted() просит передать ей правило сравнения — функцию, которая скажет, что сравнивать. Писать ради одного использования полноценную функцию с def, именем и отдельным местом в файле неудобно: три строки кода нужны один-единственный раз, прямо в этой строке.
Для таких коротких одноразовых случаев в Python есть особая форма функции — она не имеет имени и умещается в одну строку прямо там, где нужна. В официальной терминологии Python это называется лямбда-функция (lambda function) — анонимная функция, заданная одним выражением.
Синтаксис:
lambda аргументы: выражение
square = lambda x: x * x
print(square(5))
Что здесь происходит:
lambda— ключевое слово, с которого начинается анонимная функцияxперед двоеточием — параметр, как в обычной функции: значение, которое передадут при вызове- всё, что после двоеточия (
x * x), — единственное выражение, его результат и есть ответ функции square = lambda x: x * x— лямбда сама по себе является значением, поэтому её можно, как и число или строку, положить в переменнуюsquare(5)— вызываем сохранённую функцию обычными круглыми скобками, ничем не отличается от вызова функции черезdef
Вывод программы:
25
У лямбды нет слова return — и не может быть: Python запрещает его внутри лямбды синтаксически. Но значение всё равно возвращается: правило простое — лямбда всегда возвращает результат своего единственного выражения, без отдельной команды на это.
classify = lambda x: "положительное" if x > 0 else "не положительное"
print(classify(5))
print(classify(-3))
Разбор:
classifyпринимает один параметрx- после двоеточия стоит тернарный оператор — вы уже с ним знакомы: это выражение, а не отдельная команда, поэтому в лямбду оно помещается
classify(5):5 > 0истинно, выражение вычисляется в"положительное"classify(-3):-3 > 0ложно, выражение вычисляется в"не положительное"
Вывод:
положительное
не положительное
Чего в лямбде нет:
Лямбда — специально урезанная форма функции. В ней нельзя написать несколько строк, нельзя использовать if как отдельный блок (только тернарный оператор как выражение), нельзя циклы и нельзя return. Если логика не помещается в одно выражение — это сигнал, что нужен обычный def, а не лямбда.
Функция как обычное значение
В Python функция — это не что-то особенное на уровне языка, а такой же объект, как число или строка. Её можно положить в переменную, передать в другую функцию как аргумент или вернуть из функции как результат — ровно так же, как вы уже передаёте числа и списки.
Функцию, которая принимает другую функцию как аргумент (или возвращает функцию), в документации Python называют функцией высшего порядка (higher-order function). Именно такие функции и разберём дальше в этом уроке — map, filter, reduce.
def double(x):
return x * 2
def apply_twice(func, value):
return func(func(value))
print(apply_twice(double, 3))
Разбор:
apply_twice(func, value)— параметрfuncничем не отличается от обычного параметра, просто ожидает, что в него передадут функциюfunc(func(value))— сначала вызываемfuncотvalue, потом ещё раз вызываемfuncуже от результатаapply_twice(double, 3)— обратите внимание:doubleпередаётся без скобок. Скобки означают "вызови прямо сейчас", а без скобок мы передаём саму функцию как значение, чтобы вызвал её кто-то другой- внутри:
func = double, затемdouble(double(3))→double(6)→12
Вывод:
12
map() — одна операция для каждого элемента
Часто нужно применить одно и то же преобразование к каждому элементу списка: перевести все цены из долларов в рубли, привести все слова к верхнему регистру. Писать для этого цикл с накоплением результата в новый список — рабочий, но многословный способ.
Для этого в Python есть встроенная функция map(): она принимает функцию и коллекцию, и применяет эту функцию к каждому элементу коллекции по очереди.
prices_usd = [10, 25, 5, 100]
rate = 90
prices_rub = list(map(lambda p: p * rate, prices_usd))
print(prices_rub)
Разбор:
map(lambda p: p * rate, prices_usd)— первым аргументом функция (что делать с каждым элементом), вторым — коллекция (по каким элементам пройти)- лямбда
lambda p: p * rateвызывается по очереди для10,25,5,100, каждый раз параметрpполучает очередное число map()сама по себе возвращает не список, а особый объект — итератор, который умеет отдавать значения по одному, но не хранит их все сразу в памяти- поэтому результат оборачиваем в
list(...), чтобы получить обычный список, который можно напечатать или перебрать ещё раз
Вывод:
[900, 2250, 450, 9000]
Если забыть list(...) и просто напечатать map(...), вы увидите что-то вроде <map object at 0x...> — это не ошибка, просто вы напечатали сам итератор, а не значения внутри него.
filter() — оставляем только нужное
Другая частая задача — не преобразовать все элементы, а отобрать из них только те, что подходят под условие: только положительные числа, только совершеннолетних пользователей. Здесь помогает filter().
filter(функция, коллекция) проходит по коллекции и оставляет только те элементы, для которых функция вернула True. Такую функцию — ту, что возвращает только True/False, — в документации Python называют предикатом (predicate).
numbers = [1, -3, 5, -2, 8, -7, 0, 4]
positives = list(filter(lambda x: x > 0, numbers))
print(positives)
Разбор:
lambda x: x > 0— предикат: для каждогоxвозвращаетTrue, если число больше нуля, иначеFalsefilterвызывает эту лямбду для каждого элементаnumbersи пропускает дальше только те, для которых получилосьTrue- как и
map(),filter()возвращает итератор, поэтому снова оборачиваем вlist(...)
Вывод:
[1, 5, 8, 4]
map и filter отлично комбинируются друг с другом — результат одной функции можно сразу передать в другую:
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
result = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers)))
print(result)
Разбор:
- читаем изнутри наружу: сначала
filter(lambda x: x % 2 == 0, numbers)оставляет только чётные —2, 4, 6, 8 - потом
map(lambda x: x ** 2, ...)возводит каждое из оставшихся чисел в квадрат list(...)в самом конце собирает итоговый итератор в список
Вывод:
[4, 16, 36, 64]
functools.reduce() — сворачиваем коллекцию в одно число
map и filter возвращают коллекцию (пусть и меньше или преобразованную). А что, если нужен не список, а одно итоговое значение — например, сумма всех чисел? Можно представить это как стопку монет: берёте текущую стопку и кладёте сверху ещё одну монету, получаете новую стопку — и так по одной, пока монеты не кончатся.
Именно так работает reduce(): она последовательно применяет функцию к паре "накопленный результат, следующий элемент" и на каждом шаге получает новый накопленный результат. В конце остаётся одно значение. В документации Python это называют свёрткой (reduction). В отличие от map и filter, которые встроены в язык, reduce лежит в отдельном модуле functools, поэтому её нужно сначала импортировать строкой from functools import reduce.
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, numbers)
print(total)
Разбор по шагам:
| Шаг | acc | x | Новый acc |
|---|---|---|---|
| 1 | 1 (первый элемент) | 2 | 3 |
| 2 | 3 | 3 | 6 |
| 3 | 6 | 4 | 10 |
| 4 | 10 | 5 | 15 |
acc (сокращение от accumulator — накопитель) — первый параметр лямбды, он и есть текущий накопленный результат. Без начального значения первым acc становится первый элемент коллекции, а перебор начинается со второго.
Вывод:
15
Третьим аргументом можно задать начальное значение накопителя — тогда перебор начнётся с первого элемента, а не со второго:
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, numbers, 100)
print(total)
Разбор:
100— третий аргумент, начальное значениеaccперед первым шагом- перебор идёт по всем пяти элементам списка, а не по четырём, как в примере без начального значения
- итог:
100 + 1 + 2 + 3 + 4 + 5 = 115
Вывод:
115
any() и all() — хотя бы один или все сразу
Часто нужно ответить на простой вопрос: "Есть ли в списке хоть один отрицательный элемент?" или "Все ли поля формы заполнены?". Можно перебрать коллекцию циклом с флагом, а можно обойтись одной встроенной функцией.
any(iterable) возвращает True, если хотя бы один элемент коллекции истинный. all(iterable) возвращает True, только если истинны абсолютно все элементы. Обеим функциям обычно передают генератор — вы уже работали с таким синтаксисом, когда разбирали коллекции.
numbers = [5, 3, -1, 8, 2]
has_negative = any(x < 0 for x in numbers)
all_positive = all(x > 0 for x in numbers)
print(has_negative)
print(all_positive)
Разбор:
x < 0 for x in numbers— генератор: для каждогоxизnumbersвычисляетx < 0, не создавая промежуточный списокany(...)проверяет значения по очереди и останавливается, как только нашла первоеTrue— это-1, поэтому результатTrueall(...)во втором вызове проверяетx > 0для всех элементов; на-1условие ложно, поэтому весьall()сразу даётFalse
Вывод:
True
False
Пустая коллекция — частая ловушка:
any([]) вернёт False — истинных элементов и правда нет ни одного. А вот all([]) вернёт True: формально все (ноль) элементы удовлетворяют условию, раз проверять нечего. Если забыть об этом, легко получить неожиданное "всё в порядке" там, где на самом деле просто нет данных.
Частые ошибки
Попытка написать return внутри лямбды
bad = lambda x: return x * 2
SyntaxError: return — это отдельная инструкция, а тело лямбды должно быть одним выражением. Правильно: lambda x: x * 2 — без слова return, значение выражения возвращается само.
Забыли обернуть map/filter в list()
result = map(lambda x: x * 2, [1, 2, 3])
print(result)
Напечатается не [2, 4, 6], а что-то вроде <map object at 0x...>. Ошибки здесь нет — просто map() вернула итератор, а не список. Чтобы увидеть значения, нужен list(result).
reduce без импорта
total = reduce(lambda acc, x: acc + x, [1, 2, 3])
NameError: name 'reduce' is not defined. В отличие от map, filter, any, all, функция reduce не встроена в язык напрямую — её нужно сначала подключить строкой from functools import reduce.
Что важно запомнить
- lambda аргументы: выражение — анонимная функция в одну строку, результат выражения возвращается без
return - функция в Python — обычное значение: её можно передавать в другую функцию без скобок вызова, функция, принимающая функцию, называется функцией высшего порядка
- map(func, iterable) применяет
funcк каждому элементу, filter(func, iterable) оставляет только те, гдеfuncдалаTrue— обе возвращают итератор, нуженlist(...) - reduce(func, iterable) из
functoolsсворачивает коллекцию в одно значение через накопительacc - any(iterable) — True, если истинен хотя бы один элемент; all(iterable) — True, если истинны все;
all([])тоже True
Проверьте себя
6 вопросов
map и filter с lambda
Дан список чисел. Напиши функцию process_numbers(numbers), которая:
- Оставляет только чётные числа (используй
filterиlambda) - Возводит каждое из них в квадрат (используй
mapиlambda) - Возвращает итоговый список
Пример: process_numbers([1, 2, 3, 4, 5, 6]) → [4, 16, 36]