Функция как значение в Python: передача функций
map и sorted уже принимали функции в аргументах — теперь разберём приём целиком: функция такое же значение, как число или строка.
Редакция Питоники
Мы уже передавали функции, не называя этого: в map и filter отправляли правило lambda, в sorted — параметр key. Пора разобрать приём целиком, потому что на нём держится половина серьёзного Python. Секрет простой: функция в Python — такое же значение, как число или строка. Её можно присвоить переменной, положить в список, передать аргументом — и вернуть из другой функции.
Имя функции — тоже переменная
Начнём с присвоения. Имя функции без скобок — это сама функция; его можно положить в другую переменную:
def shout(text):
return text.upper() + '!'
speak = shout
print(speak('привет'))
print(shout('привет'))
ПРИВЕТ! ПРИВЕТ!
speak = shout не создала новую функцию — обе переменные смотрят на одну и ту же, и вызывать можно через любое имя. Здесь важно поймать разницу: справа стоит имя без скобок. Со скобками shout('привет') — вызов, и в переменную попала бы строка. А печатать саму функцию не стоит:
# Так печатать бессмысленно
def shout(text):
return text.upper() + '!'
print(shout)
Функция как аргумент
Раз функция — значение, её можно передать в аргументе, как любую строку или число. Функция, которая принимает или возвращает другие функции, называется функцией высшего порядка:
def apply(func, value):
return func(value)
print(apply(abs, -9))
print(apply(len, 'кот'))
def double(n):
return n * 2
print(apply(double, 21))
9 3 42
apply ничего не знает о том, что ей пришлют: в параметр func попадёт функция, и строка func(value) вызовет её с переданным значением. Один вызов посчитал модуль, второй — длину строки, третий — удвоение, и сама apply не менялась. Ровно так устроены map, filter и параметр key у sorted: принимающая функция вызывает ваш колбэк, когда ему пора работать.
Одна функция — разные стратегии
Практическая польза приёма: поведение функции можно менять, не переписывая её. Данные чистятся одинаково, а правило подготовки подставляется снаружи — такой аргумент-функцию называют колбэком:
def clean(text, fixer):
return fixer(text.strip())
def shout_style(t):
return t.upper() + '!'
def quiet_style(t):
return t.lower() + '...'
print(clean(' Привет ', shout_style))
print(clean(' Привет ', quiet_style))
ПРИВЕТ! привет...
clean знает один алгоритм — обрезать пробелы методом strip и передать дальше фиксеру. Крикливая стратегия и тихая стратегия дают из одного текста разные результаты, и код clean при этом не изменился ни на символ. Замена поведения подстановкой функции — приём, который встречается везде: правила сортировки, обработчики событий, конфигурации библиотек.
Функции в списке и в словаре
Раз функция — значение, она живёт и в наборах. Список функций перебирают циклом, словарь — берут по ключу:
def shout_style(t):
return t.upper() + '!'
def quiet_style(t):
return t.lower() + '...'
styles = [shout_style, quiet_style]
for style in styles:
print(style('День'))
ДЕНЬ! день...
ops = {
'сложить': lambda a, b: a + b,
'умножить': lambda a, b: a * b,
}
print(ops['сложить'](3, 4))
print(ops['умножить'](3, 4))
7 12
Словарь операций — маленькая замена длинной цепочки if/elif: по строковому имени достаётся правило, и оно тут же вызывается. Имена в наборы кладут без скобок — со скобками в списке оказались бы строки-результаты, а не функции.
Конвейер: функции друг за другом
Из списка функций собирается конвейер: текст проходит обработку шаг за шагом, и шаги можно собирать в любом составе:
def strip_spaces(t):
return t.strip()
def lower_all(t):
return t.lower()
def process(text, steps):
for step in steps:
text = step(text)
return text
print(process(' ДоБрЫй ДеНь ', [strip_spaces, lower_all]))
добрый день
Каждый шаг конвейера — функция, получившая текст и вернувшая текст. Хотите ещё шаг — пишете новую функцию и дописываете её в список, process не трогается. Так строятся конвейеры обработки данных и промежуточные слои в больших системах.
Функция возвращает функцию
Высший порядок работает и в обратную сторону: функция может произвести на свет новую функцию. Фабрика приветствий запоминает слово и возвращает функцию, которая здоровается этим словом:
def make_greeting(word):
def greet(name):
return f'{word}, {name}!'
return greet
hello = make_greeting('Привет')
bye = make_greeting('Пока')
print(hello('Анна'))
print(bye('Борис'))
Привет, Анна! Пока, Борис!
Два вызова фабрики дали две разные функции: одна помнит слово «Привет», другая — «Пока». Внутренняя функция greet использует переменную word из внешней — и помнит её даже после того, как фабрика отработала. Как устроена эта память под капотом — тема следующего урока о замыканиях; пока достаточно факта: возвращённая функция живёт со своими данными.
# Так не работает: функция вызывается слишком рано
def apply(func, value):
return func(value)
def shout(text):
return text.upper() + '!'
print(apply(shout(), 'привет'))
| Место | Как передаётся функция | Урок |
|---|---|---|
| map и filter | Первый аргумент: map(rule, значения) | урок про map |
| sorted и min/max | Параметр key: sorted(pairs, key=правило) | урок про sorted с key |
| Свой код | Обычный аргумент: clean(text, fixer) | этот урок |
| Обёртки и декораторы | Функция получает функцию и возвращает новую | урок про декораторы |
Осталась последняя форма самообращения: функция, которая вызывает не другую функцию, а саму себя. Это звучит как логическая ловушка, но на ней решаются задачи, недоступные простому циклу. Встречайте рекурсию — следующий урок.
callable: проверить, что перед нами
Иногда нужно убедиться, что в переменной действительно функция, а не число или строка. Встроенная функция callable отвечает на этот вопрос — и пригодится, когда функция приходит извне: из конфига, из JSON, из аргументов командной строки.
def is_even(n):
return n % 2 == 0
print(callable(is_even))
print(callable(42))
print(callable('текст'))
True False False
Функция вызвать можно, число — нет. Проверять это стоит на границе, где код принимает решение, что делать: если в конфиге вместо имени функции оказалась строка, понятный TypeError можно заменить внятным сообщением.
Колбэк с разным числом аргументов
У колбэка своя сигнатура, и если принимающая функция зовёт правило всегда с одним аргументом, то правило с двумя аргументами в неё не влезет. Есть два выхода: подстроить правило или сделать вызов гибким через *args.
def apply_all(items, rule):
return [rule(item) for item in items]
def add_ten(n):
return n + 10
print(apply_all([1, 2, 3], add_ten))
[11, 12, 13]
Правило должно принимать ровно то, что ему дают. Если правилу нужны дополнительные данные, их удобно закрыть замыканием: lambda n: add_ten(n, 10) — но это уже техника следующего урока. Второй путь — принимать колбэк с *args и передавать ему столько значений, сколько он умеет.
Свой map: пишем, чтобы понять
Мы пользуемся map с прошлого урока, но не писали его сами. Напишем — не чтобы заменить встроенный, а чтобы увидеть механику: колбэк вызывается изнутри цикла, по одному разу на элемент.
def my_map(items, rule):
result = []
for item in items:
result.append(rule(item))
return result
def to_upper(text):
return text.upper()
print(my_map(['хлеб', 'сыр'], to_upper))
['ХЛЕБ', 'СЫР']
Встроенный map делает ровно то же самое, только внутри интерпретатора и быстрее. Собственный вариант полезен не скоростью, а пониманием: после него map перестаёт быть магией — вы знаете, что внутри цикла стоит вызов колбэка. Такой код мы пишем один раз, чтобы понять, и дальше пользуемся встроенным.
| Что написали | Что получилось | Как правильно |
|---|---|---|
| apply(shout, x) | Передана сама функция | то, что нужно |
| apply(shout(), x) | Вызов без аргумента, TypeError | apply(shout, x) |
| [shout_style()] | Список из результатов вызовов | [shout_style] |
| print(shout) | Служебная запись с адресом памяти | print(shout('привет')) |
| ops['сложить'] | Сама функция, а не результат | ops'сложить' |
Пять строк — и пять разных способов ошибиться в одном месте: со скобками и без них. Мы держим в голове простое правило: скобки означают «выполни сейчас». Всё, что нужно передать дальше, пишется без скобок.
Практика: один отчёт, два формата
Соберём вместе: данные приходят списком, а как именно их показать — решает переданная функция.
def format_report(title, rows, formatter):
print(title)
for row in rows:
print(formatter(row))
def as_line(row):
return ' - '.join(row)
def as_csv(row):
return ','.join(row)
data = [['чай', '120'], ['кофе', '350']]
format_report('Прайс', data, as_line)
format_report('Прайс', data, as_csv)
Прайс чай - 120 кофе - 350 Прайс чай,120 кофе,350
Один и тот же отчёт — два формата: текст для человека и строка для выгрузки. Код печати не изменился, изменилось только то, что мы передали. Ровно этот приём лежит в основе шаблонов отчётов, сортировки по правилу и любых настроек поведения.
Сначала предскажи ответ в голове — это главный навык программиста.
def apply(func, value):
return func(value)
print(apply(str.upper, 'чай'))
def double(n):
return n * 2
f = double
print(f(5))
ops = {'минус': lambda a, b: a - b}
print(ops['минус'](10, 4))
1. Что такое имя функции без скобок, например shout?
2. Что вернёт apply(len, 'ок') у функции def apply(func, value): return func(value)?
3. Какая функция называется функцией высшего порядка?
4. Что попадёт в переменную при записи speak = shout()?
5. Что лежит в списке styles = [shout_style, quiet_style]?
6. Почему hello из make_greeting('Привет') печатает именно Привет, Анна?
7. Что вернёт callable(42)?
8. Что вернёт apply(len, [1, 2, 3]) у функции def apply(func, value): return func(value)?
9. Почему список styles = [shout_style()] содержит не функции?
Напиши функцию apply_twice(func, value), которая применяет func к value два раза подряд, и напечатай результат для функции double(n): n * 2 и числа 3.
Как передать функцию в функцию в Python?
Именем без скобок: apply(shout, 'привет') передаёт саму функцию, а принимающая сторона вызовет её через параметр: func(value). Запись со скобками apply(shout(), 'привет') вызовет функцию немедленно и передаст результат — классическая ловушка начинающих.
Урок 7: map — функция для каждого элементаЧто такое функции высшего порядка?
Функции, которые принимают или возвращают другие функции: map и filter применяют переданное правило к элементам, sorted с параметром key сортирует по правилу, а фабрика вроде make_greeting возвращает новую функцию. На этом приёме построены декораторы Python.
Урок 16: декораторы — обёртка вокруг функцииЧто такое колбэк?
Функция-стратегия, которую вы передаёте, а вызывает её чужой код, когда пора: clean(text, fixer) вызывает fixer внутри себя. Колбэки позволяют менять поведение без переписывания принимающей функции — тот же принцип, что у key в sorted и правила в map.
Можно ли хранить функции в списке или словаре?
Да: функции — значения. Список styles = [shout_style, quiet_style] перебирают циклом и вызывают каждый элемент; словарь ops = {'сложить': lambda a, b: a + b} даёт выбор правила по строковому ключу. Главное — класть имена без скобок, чтобы в наборе оказались функции, а не результаты вызовов.
Как проверить, что в переменной лежит именно функция?
Встроенной функцией callable: callable(is_even) даёт True, callable(42) — False. Проверять стоит там, где код получает функцию извне — из конфига или аргументов командной строки — и должен понять, вызывать её или ругаться.
Что будет, если правило-колбэк принимает два аргумента, а вызов даёт один?
Произойдёт TypeError: не хватает аргумента. Решается это тремя способами: сделать правило одноаргументным, закрыть лишние данные замыканием или принять колбэк с *args и передавать ему столько значений, сколько он умеет.
Чем функция-значение отличается от результата её вызова?
Тем, что одно — объект, а другое — данные. Имя функции без скобок даёт сам объект, его можно хранить и передавать; вызов со скобками даёт результат, который можно только использовать как данные. Один символ — и совершенно разные вещи.
Зачем хранить функции в словаре, если можно написать if/elif?
Словарь даёт выбор правила по строковому имени: добавить новый вариант — одна строка в словаре, а не новая ветка в цепочке условий. Длинные цепочки if/elif с каждым новым случаем растут, а словарь остаётся плоским и читается как таблица.
Чем колбэк отличается от обычного аргумента?
Обычный аргумент — данные, колбэк — код. Разница видна по месту скобок: вычисление значения пишут в скобках, а функцию передают именем без скобок, и вызывает её принимающая сторона сама.
Понравился урок? Сошлитесь на него
«Имя функции со скобками — это вызов прямо сейчас, а имя без скобок — сама функция, которую можно передать дальше.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Функции · Урок 6
Lambda в Python: функция в одну строку
lambda — правило без имени: аргументы до двоеточия, одно выражение после. Учимся писать короткие правила и передавать их в другие функции аргументом.
Функции · Урок 12
Встроенные функции Python: инструментальный ящик
Десятки функций лежат в Python наготове: len, sum, min, max, abs, round, enumerate, zip, input. Обзор инструментального ящика — что чем открывать.
Функции · Урок 15
Замыкания в Python: функция с памятью
Функция, которая помнит место своего рождения: делаем счётчик на замыкании, разбираем nonlocal и ловим классическую ловушку с циклом.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
json · Урок 19
Мини-клиент API на функциях
URL, разбор ответа, фильтр, файл — каждый шаг уже умещается в одну функцию. Собираем их в клиент, который читается как сценарий.
python клиент api функциифункции python для обработки json
ООП · Урок 17
Декораторы: функции над функциями
Декоратор — функция, которая принимает функцию и возвращает новую, добавляя поведение без правки исходного кода. Соберём декоратор с нуля: от замыкания и синтаксиса @ до functools.wraps, декораторов для методов и декораторов с параметрами.
функция как аргумент python
Функции · Урок 2
Аргументы функции в Python: данные внутрь
Функция принимает данные через параметры: greet('Анна') здоровается с Анной. Разбираем позиционные аргументы, порядок и количество — и типичную ошибку TypeError.
python аргументы функцииаргументы функции python