4.2. Позиционные и именованные аргументы. Функции высших порядков. Лямбда-функции

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

Ключевые вопросы параграфа

  • Что такое позиционные и именованные аргументы и как они передаются в функцию?
  • Зачем нужны значения по умолчанию и какие ошибки могут возникнуть при их использовании?
  • Как функция может принимать произвольное количество аргументов с помощью *args и **kwargs?
  • Что такое функции высших порядков и как передавать функции в качестве аргументов?
  • Как устроены лямбда-функции и когда их удобно применять в Python?

Чем различаются позиционные и именованные аргументы

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

Рассмотрим пример. Напишем функцию, которая считает окончательную стоимость товара с учётом скидки в процентах:

1def final_price(price, discount):
2    return price - price * discount / 100
3
4print(final_price(1000, 5))
5
6# Вывод программы:
7# 950.0

Зачем нужны значения по умолчанию и какие ошибки могут возникнуть при их использовании

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

В следующем примере аргумент discount по умолчанию равен 1 — то есть скидка будет 1%, если другое значение не указано.

1def final_price(price, discount=1):
2    return price - price * discount / 100
3
4
5print(final_price(1000, 5))
6# Значение скидки не задано, используется значение по умолчанию
7print(final_price(1000))
Вывод программы:
950.0
990.

Обратите внимание: значение по умолчанию присваивается один раз — в момент объявления функции. Если значение — объект изменяемого типа (например, список), это может привести к неожиданному поведению.

Рассмотрим пример, в котором аргумент по умолчанию — список. Функция добавляет значение x в список и возвращает его:

1def add_value(x, list_arg=[]):
2    list_arg += [x]
3    return list_arg
4
5print(add_value(0))
6print(add_value(0, [1, 2, 3]))
7print(add_value(1))
Вывод программы:
[0]
[1, 2, 3, 0]
[0, 1]

Что здесь произошло?

  • Первый вызов add_value(0) добавил значение в пустой список, созданный по умолчанию.
  • Второй вызов использует явно переданный список, поэтому работает ожидаемо.
  • А вот третий снова использует значение по умолчанию — но оно уже содержит элемент 0 от первого вызова.

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

1def add_value(x, list_arg=None):
2    if list_arg is None:
3        list_arg = []
4    list_arg += [x]
5    return list_arg
6
7print(add_value(0))
8print(add_value(0, [1, 2, 3]))
9print(add_value(1))
Вывод программы:
[0]
[1, 2, 3, 0]
[1]

Теперь каждый вызов работает независимо, и никакие старые значения не сохраняются.

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

Посмотрим, как это работает, на примере функции final_price:

1def final_price(price, discount=1):
2    return price - price * discount / 100
3
4print(final_price(1000, discount=5))
5print(final_price(discount=10, price=1000))
Вывод программы:
950.0
900.0

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

Обратите внимание: позиционные аргументы всегда должны идти первыми, а именованные — после них.

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

Как функция может принимать произвольное количество аргументов с помощью *args и **kwargs

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

Позиционные аргументы: *args

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

Примером такой гибкой функции является print(), которая принимает сколько угодно аргументов.

Модифицируем знакомую нам функцию final_price(), чтобы она могла рассчитывать скидку сразу для нескольких цен:

1def final_price(*prices, discount=1):
2    return [price - price * discount / 100 for price in prices]
3
4print(final_price(100, 200, 300, discount=5))
Вывод программы:
[95.0, 190.0, 285.0]

Функция получила три значения в *prices, применила скидку к каждому и вернула результат в виде списка.

Именованные аргументы: **kwargs

Если нужно принимать любое количество именованных аргументов, используется ** перед именем параметра — обычно kwargs (англ. keyword arguments). Такой параметр внутри функции представляет собой словарь, в котором:

  • ключи — это имена переданных аргументов;
  • значения — это их соответствующие значения.

Допустим, мы хотим добавить в функцию final_price() фильтр: рассчитывать скидку только для цен в определённом диапазоне. Границы диапазона (price_low и price_high) передаются как именованные аргументы через **kwargs.

1def final_price(*prices, discount=1, **kwargs):
2    low = kwargs.get("price_low", min(prices))
3    high = kwargs.get("price_high", max(prices))
4    return [price - price * discount / 100 for price in prices if low <= price <= high]
5
6print(final_price(100, 200, 300, 400, 500, discount=5))
7print(final_price(100, 200, 300, 400, 500, discount=5, price_low=200))
8print(final_price(100, 200, 300, 400, 500, discount=5, price_high=200))
9print(final_price(100, 200, 300, 400, 500, discount=5, price_low=200, price_high=350))

Вывод программы:

[95.0, 190.0, 285.0, 380.0, 475.0]
[190.0, 285.0, 380.0, 475.0]
[95.0, 190.0]
[190.0, 285.0]

Что здесь происходит:

  • Если price_low или price_high не переданы, берутся минимальное и максимальное значения из *prices.
  • Функция фильтрует цены по диапазону и рассчитывает скидку только для подходящих значений.

Что такое функции высших порядков и как передавать функции как аргументы

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

Функции, которые принимают другие функции как аргументы, называют функциями высшего порядка.

Функция filter()

Функция filter() позволяет отобрать элементы из итерируемого объекта по заданному критерию. В качестве критерия передаётся функция, возвращающая логическое значение (True или False). Результат filter() — это итератор, содержащий только те элементы, для которых функция-критерий вернула True.

Пример: отбираем из списка только положительные числа.

1def only_pos(x):
2    return x > 0
3
4result = filter(only_pos, [-1, 5, 6, -10, 0])
5print(", ".join(str(x) for x in result))

Вывод программы:

5, 6

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

Пример: отбираем из строки только буквы, используя метод str.isalpha().

1result = filter(str.isalpha, "123ABcd()")
2print("".join(result))

Вывод программы:

ABcd

Функция map()

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

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

1def square(x):
2    return x ** 2
3
4result = map(square, range(5))
5print(", ".join(str(x) for x in result))

Вывод программы:

0, 1, 4, 9, 16

Как и в filter(), в map() можно передавать встроенные функции и методы.

Пример: преобразуем все строки в списке к нижнему регистру с помощью str.lower().

1result = map(str.lower, ["abCD", "EFGh", "IJkl"])
2print("\n".join(result))

Вывод программы:

abcd
efgh
ijkl

Типичный приём: преобразование ввода

Один из частых примеров использования map() — преобразование пользовательского ввода:

1numbers = list(map(int, input().split()))

Эта строка кода:

  • считывает строку из ввода,
  • разбивает её по пробелам,
  • преобразует каждое значение в int,
  • собирает всё в список.

Как устроены лямбда-функции и когда их удобно применять в Python

Когда нужно передать небольшую функцию в качестве аргумента, например, в map() или filter(), не всегда удобно отдельно объявлять её с помощью def. В таких случаях в Python используют лямбда-функции — компактный способ описать функцию прямо внутри вызова другой функции.

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

Синтаксис

Объявление лямбда-функции выглядит так:

1lambda x: x > 0
  • lambda — ключевое слово;
  • x — аргумент функции (может быть несколько);
  • после двоеточия — возвращаемое значение (без return).

Такая запись создаёт функцию, которая возвращает True, если x > 0.

Использование в filter() и map()

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

1result = filter(lambda x: x > 0, [-1, 5, 6, -10, 0])
2print(", ".join(str(x) for x in result))

Вывод программы:

5, 6

А теперь применим lambda к другому случаю — например, возведению чисел в квадрат:

1result = map(lambda x: x ** 2, range(5))
2print(", ".join(str(x) for x in result))

Вывод программы:

0, 1, 4, 9, 16

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

Сортировка с помощью sorted() и лямбда-функции

Функции sorted(), sort(), min() и max() принимают именованный аргумент key — функцию, которая вычисляет ключ для сравнения элементов. Часто в роли такой функции удобно использовать лямбду.

Пример: сортировка строк по длине:

1lines = ["abcd", "ab", "abc", "abcdef"]
2print(sorted(lines, key=lambda line: len(line)))

Вывод программы:

['ab', 'abc', 'abcd', 'abcdef']

Сортировка по нескольким критериям

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

1lines = ["abcd", "ab", "ba", "acde"]
2print(sorted(lines, key=lambda line: (len(line), line)))

Вывод программы:

['ab', 'ba', 'abcd', 'acde']

Пример: по убыванию длины, а при равенстве — по алфавиту:

1lines = ["abcd", "ab", "ba", "acde"]
2print(sorted(lines, key=lambda line: (-len(line), line)))

Вывод программы:

['abcd', 'acde', 'ab', 'ba']

Применение в min() и max()

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

1lines = ["abcd", "ab", "ba", "acde"]
2print(min(lines, key=lambda line: (-len(line), line)))

Вывод программы:

abcd

Альтернатива: списочные выражения

Часто то, что можно сделать с map() или filter(), проще выразить через списочные выражения — они более наглядны и читаемы:

1result = (x for x in [-1, 5, 6, -10, 0] if x > 0)
2print(", ".join(str(x) for x in result))

Вывод программы:

5, 6

Что лучше использовать — map() / filter() или списочные выражения? Это вопрос вкуса. Создатель Python Гвидо ван Россум предлагал отказаться от map() и filter() в пользу более читаемых списочных выражений.

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

Ещё одна функция высшего порядка, где часто используют лямбда-функции, — это reduce() из модуля functools. Она кумулятивно (с накоплением) применяет заданную функцию к элементам итерируемого объекта и возвращает одно значение.

Другими словами, reduce() «сворачивает» последовательность в одно итоговое значение.

Пример: найдём сумму чисел от 1 до 5 с помощью reduce() и лямбда-функции:

1from functools import reduce
2numbers = range(1, 6)
3print(reduce(lambda x, y: x + y, numbers))

Вывод программы:

15

Здесь reduce() последовательно складывает элементы:

 (((1 + 2) + 3) + 4) + 5 → 15.

Такой подход удобно использовать и для других операций — например, произведения, конкатенации строк, поиска минимума и т. д.

✅ У вас получилось разобраться, как работать с аргументами и функциями высших порядков в Python?

👉 Оценить этот параграф

Что дальше

Теперь вы знаете, как передавать аргументы в функции разными способами — позиционно, по имени и с использованием значений по умолчанию. Вы разобрались, как функции могут принимать любое количество аргументов с помощью *args и **kwargs, и научились передавать одни функции в другие.

Вы познакомились с функциями высшего порядка — такими, как map(), filter() и reduce() — и узнали, как с помощью лямбда-выражений можно кратко описывать нужную логику прямо в вызове функции.

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

А пока вы не ушли дальше — закрепите материал на практике:

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

Хотите обсудить, задать вопрос или не понимаете, почему код не работает? Мы всё предусмотрели — вступайте в сообщество Хендбука! Там студенты помогают друг другу разобраться.

Ключевые выводы параграфа

  • Параметры функции можно передавать как позиционно, так и по имени — это даёт гибкость при вызове функций.
  • Значения по умолчанию удобны, но с изменяемыми объектами (например, списками) нужно быть осторожным.
  • *args и **kwargs позволяют передавать в функцию неограниченное количество аргументов.
  • Функции высшего порядка принимают другие функции в качестве аргументов. Среди них — map(), filter(), sorted(), min(), reduce().
  • Лямбда-функции — компактный способ описать поведение прямо в теле вызова. Их удобно использовать для сортировки и обработки коллекций.

Отмечайте параграфы как прочитанные чтобы видеть свой прогресс обучения

Вступайте в сообщество хендбука

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

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

Следующий параграф4.3. Рекурсия. Декораторы. Генераторы

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