Функции высшего порядка и callbacks в Python
Функция в Python — такой же объект, как строка или список: её можно присвоить переменной, положить в коллекцию, передать аргументом и вернуть из другой функции. Функцию, которая принимает или возвращает функции, называют функцией высшего порядка. Переданная для последующего вызова функция играет роль callback: вызывающий код решает, когда её запустить, а callback определяет, что именно вычислить.
Функция как значение
Имя функции без круглых скобок обозначает сам объект функции. Запись normalize можно передать в map, а запись normalize(' Python ') немедленно вызывает функцию и даёт строковый результат. Это различие особенно важно в аргументах key, обработчиках событий и собственных функциях-обходчиках.
У callback должен быть понятный контракт: какие аргументы он получает, что возвращает и может ли иметь побочные эффекты. Например, sorted(items, key=selector) вызывает selector по одному разу для каждого элемента и сортирует по возвращённым ключам. Сам callback не должен переставлять элементы списка.
Рабочий пример: настраиваем отчёт
Функция build_report ничего не знает о правилах скидки. Она получает правило извне и применяет его к каждой цене.
def student_price(price):
return round(price * 0.8)
def build_report(prices, transform):
changed = [transform(price) for price in prices]
return changed, sum(changed)
final_prices, total = build_report([500, 1200, 300], student_price)
print(final_prices)
print(total)Ожидаемый результат:
[400, 960, 240]
1600Вызов build_report(..., student_price) передаёт функцию, а не результат одного заранее выполненного расчёта. Благодаря этому тот же обход можно использовать с другим именованным callback или короткой lambda, если правило действительно помещается в одно выражение.
Callback вызван слишком рано
Характерная ошибка — написать build_report(prices, student_price()). Python пытается вызвать student_price прямо в месте передачи и сообщает TypeError о пропущенном аргументе price. Если аргумент случайно был указан, в build_report попадёт число, а позднее появится сообщение вроде 'int' object is not callable.
Читайте traceback от нижней строки к месту своего вызова и проверьте значение callback: print(callable(transform), transform). Если ожидается функция, callable должен вернуть True. Затем сопоставьте сигнатуру с местом вызова: выражение transform(price) требует callback, принимающий один позиционный аргумент. Для sorted(..., key=...) правило такое же: key получает один элемент, а не два сравниваемых объекта.
Тренировка: фильтрация через predicate
Напишите select_words(words, predicate), которая возвращает только слова, для которых predicate(word) истинно. Проверьте её с отдельной функцией is_long для длины не меньше пяти и с lambda word: word.startswith('п'). Для списка ['код', 'питон', 'цикл', 'практика'] заранее выпишите оба ожидаемых результата.
Самостоятельная проверка: добавьте счётчик вызовов внутрь именованного callback и убедитесь, что он запускается ровно по одному разу на элемент. После этого намеренно передайте is_long(), объясните traceback до исправления и верните передачу имени без скобок.
Контракт функции как ориентир
Круглые скобки выполняют функцию, а имя без скобок позволяет передать её как значение. Функция высшего порядка управляет общим алгоритмом, callback поставляет изменяемое правило. Чем точнее контракт аргументов и результата, тем легче переиспользовать такой код и локализовать ошибку. lambda не обладает особыми возможностями: это компактная функция из одного выражения, полезная для короткого локального преобразования.
Callback без скрытой магии
Callback обязательно должен быть lambda-функцией?
Нет. Обычная функция через def часто лучше: у неё есть имя, документация и удобный traceback. lambda уместна, когда выражение короткое и не требует повторного использования.
Чем callback отличается от результата функции?
Callback можно вызвать позже и с разными аргументами. Результат уже вычислен. Сравните key=str.lower, где передана функция, и str.lower(), где предпринят немедленный вызов без нужной строки.
Может ли функция высшего порядка вернуть новую функцию?
Да. Так создают фабрики функций и замыкания: внешняя функция сохраняет параметры настройки, а возвращённая применяет их при будущих вызовах. Следует явно назвать такой контракт, чтобы объект функции не приняли за готовое значение.