Функции высшего порядка: функции, которые получают и возвращают функции
- Объяснить, что такое функция высшего порядка (Higher-Order Function) и почему она возможна в Python
- Передавать функцию как параметр — без скобок — и вызывать её изнутри другой функции
- Использовать map, filter и параметр key в sorted, max и min
- Писать функцию, которая возвращает функцию
Что мы изучим?
- Что такое функция высшего порядка (Higher-Order Function).
- Как передать функцию как параметр — и почему без скобок.
- Встроенные функции
mapиfilter. - Параметр
keyвsorted,maxиmin. - Функцию, которая возвращает функцию.
Зачем это нужно?
Две почти одинаковые функции:
def double_all(numbers):
result = []
for n in numbers:
result.append(n * 2)
return result
def square_all(numbers):
result = []
for n in numbers:
result.append(n * n)
return resultЦикл одинаковый — отличается только действие над каждым элементом. Каждое новое действие (половина, плюс 10, округление…) требует ещё одной целой функции, а это именно то дублирование, которое запрещает DRY.
Идея: написать цикл один раз, а само действие передавать как параметр.
Объяснение с основ
Функция — это объект
Мы уже видели в уроке «функции подробно», что в Python функция — объект, как число или список:
double(без скобок) — сама функция. Её можно сохранить в переменной или передать дальше.double(5)(со скобками) — вызов функции. Результат — то, что она возвращает.
Функция высшего порядка
Функция высшего порядка (Higher-Order Function) — функция, которая:
- Получает другую функцию как параметр, или
- Возвращает функцию.
Одной вы уже пользовались: sorted(products, key=lambda p: p[1]) — sorted получает функцию в параметре key.
Стиральная машина умеет «пройти по всей одежде». Что делать с каждой вещью — вы выбираете программой: деликатная стирка, обычная или сушка. Вы не покупаете новую машину для каждой программы — вы даёте ей программу. Функция высшего порядка — это машина, а функция, которую вы передаёте, — программа.
Синтаксис
def apply_to_all(numbers, action): # action is a function
result = []
for n in numbers:
result.append(action(n)) # call it for each item
return result
apply_to_all([1, 2, 3], double) # pass it: no parentheses
list(map(double, [1, 2, 3])) # built-in: map
list(filter(is_even, [1, 2, 3, 4])) # built-in: filter
sorted(words, key=len) # key: sort by the result of lenПервый пример кода
week-2/apply_to_all.py:
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))Вывод:
[2, 4, 6]
[1, 4, 9]
[2, 2]Разбор кода построчно
| Строка | Объяснение |
|---|---|
def apply_to_all(numbers, action): | action — обычный параметр, только мы ожидаем, что в нём будет функция. |
result.append(action(n)) | Вызываем полученную функцию для каждого элемента. |
apply_to_all([1, 2, 3], double) | Передаём double без скобок — функцию, а не её результат. |
lambda n: n * n | Короткая функция без имени, созданная на месте. |
round | Можно передать и встроенную функцию. |
Ещё один пример
map, filter и key. week-2/hof_builtins.py:
prices = [100, 250, 40, 80]
with_fee = list(map(lambda p: p + 10, prices))
cheap = list(filter(lambda p: p < 100, prices))
print(with_fee)
print(cheap)
words = ["banana", "fig", "cherry", "kiwi"]
print(sorted(words, key=len))
print(max(words, key=len))
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(max(students, key=lambda s: s[1]))Вывод:
[110, 260, 50, 90]
[40, 80]
['fig', 'kiwi', 'banana', 'cherry']
banana
('Dana', 92)map(func, items)— запускаетfuncдля каждого элемента.list(...)превращает результат в список.filter(func, items)— оставляет только элементы, для которыхfuncвозвращаетTrue.key=len—sortedиmaxсравнивают по результатуlenдля каждого слова, а не по самому слову.
Функция, которая возвращает функцию. week-2/make_multiplier.py:
def make_multiplier(factor):
def multiply(n):
return n * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))
print(triple(5))Вывод:
10
15make_multiplier — «фабрика», которая производит функции: каждый вызов возвращает новую функцию, которая помнит свой factor. Как именно она помнит — увидим в следующем уроке, о вложенных функциях.
Типичная ошибка
Ошибка: вызвать функцию вместо того, чтобы передать её.
apply_to_all([1, 2], double())TypeError: double() missing 1 required positional argument: 'n'Почему? double() со скобками вызывает функцию немедленно — без параметра, — поэтому она падает ещё до того, как начнётся apply_to_all. Передавайте функцию без скобок: apply_to_all([1, 2], double). Вызовет её та функция, которая её получила.
Практика
Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.
Упражнение 1: Функция как параметр
Напишите функцию apply_to_all(numbers, action), которая запускает action для каждого числа и возвращает новый список. Используйте её с обычной функцией double, с lambda, которая возводит в квадрат, и с round.
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))Ожидаемый вывод:
[2, 4, 6]
[1, 4, 9]
[2, 2]Показать решение
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))[2, 4, 6]
[1, 4, 9]
[2, 2]Передавайте функцию без скобок (double, round). Вызывает её apply_to_all.
Упражнение 2: map и filter
prices = [20, 75, 120, 45, 90]- Оставьте только цены выше 50 с помощью
filter. - Прибавьте 10 к каждой оставшейся цене с помощью
map.
Ожидаемый вывод:
[75, 120, 90]
[85, 130, 100]Показать решение
prices = [20, 75, 120, 45, 90]
expensive = list(filter(lambda p: p > 50, prices))
raised = list(map(lambda p: p + 10, expensive))
print(expensive)
print(raised)[75, 120, 90]
[85, 130, 100]filter оставляет только то, для чего lambda возвращает True. map запускает lambda для каждого элемента. list(...) превращает результат в список.
Упражнение 3: key — по чему сортировать
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]- Отсортируйте слова по длине.
- Найдите самое длинное слово.
- Отсортируйте студентов по оценке, от высшей к низшей, и напечатайте только имена.
Ожидаемый вывод:
['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
AviПоказать решение
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(sorted(words, key=len))
print(max(words, key=len))
for name, grade in sorted(students, key=lambda s: s[1], reverse=True):
print(name)['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
Avikey=len — сравнивать по результату len для каждого слова, а не по самому слову.
Упражнение 4: Функция, которая возвращает функцию
Напишите функцию make_discount(percent), которая возвращает функцию, получающую цену и возвращающую её после скидки.
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))Ожидаемый вывод:
180.0
100.0Показать решение
def make_discount(percent):
def apply(price):
return price - price * percent / 100
return apply
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))180.0
100.0make_discount — «фабрика»: каждый вызов производит новую функцию, которая помнит свой percent.
Упражнение 5: Вызов вместо передачи
def double(n):
return n * 2
print(apply_to_all([1, 2], double()))- Какую ошибку вы получите и почему?
- Как исправить?
Показать решение
TypeError: double() missing 1 required positional argument: 'n'. Скобки вdouble()вызывают функцию немедленно — без параметра — ещё до того, как начнётсяapply_to_all.- Передайте саму функцию, без скобок:
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2], double))[2, 4]Итоги
- Функция высшего порядка = функция, которая получает функцию или возвращает функцию. Это возможно, потому что функция — объект.
- Передавайте функцию без скобок — вызовет её та функция, которая её получила.
map— действие над каждым элементом.filter— оставляет то, что удовлетворяет условию.key=вsorted,max,min— задаёт, по чему сравнивать.- Функция может возвращать функцию — «фабрика», которая производит настроенные функции.