Функциональное программирование
lambda, ФВП, map(), reduce(), filter(), zip(), enumerate()
Введение
Функциональное программирование — это такая методика написания программ, когда в центре внимания находятся функции. Функции могут присваиваться переменным, они могут передаваться в другие функции и порождать новые функции. Это можно назвать стилем или парадигмой программирования
В этой парадигме широко используются функции высшего порядка и анонимные функции
Функции высшего порядка (ФВП) — это функции, которые принимают в качестве аргумента другие функции и/или возвращают функции в качестве результата. Мы коснулись их в прошлом уроке, а в этой главе познакомимся поближе
Анонимные функции (lambda-функции) — безымянные функции
Анонимные функции (lambda)
Вспомним:
Функции — это небольшие подпрограммы внутри основного кода. При запуске они могут взять какие-то стартовые параметры, обработать их, получить свой результат и отдать этот результат в основной код
Чтобы вызвать функцию, нужно указать её имя, но бывают такие функции, у которых нет имени, но их всё равно можно вызывать
Пример обычной функции:
def add_func(x, y):
result = x + y
return result
print(add_func(2,3))
У функции есть имя, тело функции и есть результат, который она возвращает. Чтобы вызвать эту функцию, мы указываем имя и аргументы, которые нужно обработать.
Мы можем значительно усложнить функцию, для этого добавить в неё нужные инструкции
Лямбда-функции выглядят по-другому — они не имеют имени, но у них есть ключевое слово lambda.
lambda аргументы: выражение (значение функции)
result = lambda x, y: x + y
print(result(2, 3))
лямбда-функция всегда возвращает значение
она должна быть записана в одну строку
внутри функций нет присваивания и сложной логики — это всегда простое выражение
Зачем нужны лямбда-функции?
Самое частое применение — в качестве аргументов в других функциях
Пример:
def filter_numbers(numbers, filter_func):
result = []
for num in numbers:
if filter_func(num):
result.append(num)
return result
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered_numbers = filter_numbers(numbers, lambda x: x % 2 == 0) # передаём анонимную функцию в качестве аргумента
print(filtered_numbers)
# [2, 4, 6, 8, 10]
Пойдём дальше — мы можем вместо
filter_numbers
тоже использовать лямбда-функцию:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered_numbers = lambda lst: [num for num in lst if num % 2 == 0]
# здесь анонимная функция принимает lst и формирует список при помощи спискового включения
print(filtered_numbers(numbers))
# [2, 4, 6, 8, 10]
Технически, можно обойтись без лямбда-функций. Но иногда с ними получается удобнее
Практика:
Напишите четыре анонимные функции. Они должны:
Возводить переданное число в квадрат
Умножать два переданных числа
Возвращать бОльшее из двух переданных чисел
Возвращать первую букву переданного слова
Выведите на экран результат работы этих функций
Встроенные ФВП: map() filter() reduce()
map()
Допустим, мы хотим применить к каждому элементу списка какую-то функцию или операцию. Мы можем сделать при помощи цикла for:
numbers = [1, 2, 3, 4, 5]
new_numbers = []
for num in numbers:
squared = num ** 2
subtracted = squared - 10
new_numbers.append(subtracted)
print(new_numbers)
# [-9, -6, -1, 6, 15]
Можем определить две функции, одна из которых принимает последовательность и функцию, а вторая является той функцией, которую нужно применить:
def func_1(numbers, func):
res = []
for num in numbers:
res.append(func(num))
return res
def func_2(number):
number **= 2
number -= 10
return number
numbers = [1, 2, 3, 4, 5]
print(func_1(numbers, func_2))
# [-9, -6, -1, 6, 15]
Есть третий способ — использовать встроенную функцию высшего порядка map()
Она принимает в качестве аргументов функцию и последовательность. И применяет переданную функцию к каждому элементу последовательности
def func_2(number):
number **= 2
number -= 10
return number
numbers = [1, 2, 3, 4, 5]
print(list(map(func_2, numbers))) # преобразуем в list, т.к. map возращает объект типа map
# [-9, -6, -1, 6, 15]
Часто вместе в качестве аргументов для ФВП используют анонимные функции:
numbers = [1, 2, 3, 4, 5]
print(list(map(lambda n: n ** 2 - 10, numbers)))
# [-9, -6, -1, 6, 15]
filter()
filter(function or None, iterable) -> filter object
Как мы видим из сигнатуры, функция принимает функцию или None и итерируемую последовательность. Возвращает объект
filter
numbers = [2, 3, 8, 15, 34, 42]
def is_even(number):
return number % 2 == 0
filter(is_even, numbers)
list(filter(is_even, numbers))
# [2, 8, 34, 42]
filter()
принимает функцию, которая возвращает булево значениеПопробуйте передать в качестве аргумента вместо функции значение None. Проанализируйте результат
reduce()
Для того, чтобы воспользоваться функцией
reduce()
нам понадобится импортировать её из модуляfunctools
from functools import reduce
reduce(function, iterable[, initializer]) -> value
Функция
reduce()
кумулятивно применяет функциюfunction
к элементам итерируемойiterable
последовательности, сводя её к единственному значениюПо сути результат работы
reduce()
это аккумулятор
reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])
# 15
items = [10, 20, 30, 40, 50]
sum_all = reduce(lambda x, y: x + y, items)
sum_all
# 150
items = [1, 24, 17, 14, 9, 32, 2]
all_max = reduce(lambda a, b: a if (a > b) else b, items)
all_max
# 32
Необязательный аргумент
initializer
— это значение аккумулятора по умолчанию, с которого начать работу. Если последовательность пустая, аккумулятор примет значениеinitializer
. Если последовательность непустая, тоinitializer
станет начальным значением аккумулятораПопробуйте применить аргумент
initializer
к примерам выше. Проанализируйте результат
Особенности объектов map, filter, reduce
Результат работы функций
map() filter() reduce()
это объектыmap filter reduce
соответственно. Это не готовые последовательности, а итераторы — интерфейсы для взаимодействия с последовательностями, но не сами последовательности.Мы можем использовать их как
iterable
объекты, но не можем увидеть всю последовательность.Сама последовательность скрыта и данные предоставляются "по запросу" — то есть только тогда, когда это потребуется
Это позволяет эффективнее использовать память, чем при хранении, например, списка
Если нам нужна сама последовательность, результат работы
map() filter() reduce()
нужно преобразовать в неё
c = reduce(lambda x, y: x + y, map(lambda x: x * 2, [1, 2, 3, 4, 5]))
# будет работать, т.к. объект map является iterable
res = map(lambda x: x * 2, [1, 2, 3, 4, 5])
res
# map object
list(res)
# [2, 4, 6, 8, 10]
Практика:
Задание с числами:
Входные данные: список чисел.
Используйте
map()
, чтобы возвести каждое число в квадрат.Затем к получившейся последовательности примените
filter()
, чтобы отфильтровать только нечётные числа.И к получившейся в результате последней операции последовательности примените
reduce()
, чтобы сложить оставшиеся числа в списке.
Задание с текстом:
Входные данные: список строк. Также как в прошлом задании, последовательно примените функции к списку строк.
Используйте
map()
, чтобы преобразовать каждую строку в верхний регистр.Используйте
filter()
, чтобы отфильтровать строки, которые содержат определенную подстрокуИспользуйте
reduce()
, чтобы объединить отфильтрованные строки в одну строку через запятую.Верните результат.
Для тестирования используйте следующие данные:
['I love Python', 'You love Python', 'We love Python', "I don't love anything"]
Подстрока: 'PYTHON'
Некоторые полезные функции
zip()
Функция
zip()
— объединяет отдельные элементы нескольких последовательностей в кортежи. Возвращает объектzip
zip(последовательность, последовательность, ...)
Примеры использования:
x = 'абв'
y = 'эюя'
zipped = zip(x, y)
list(zipped)
# [('а', 'э'), ('б', 'ю'), ('в', 'я')]
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
zipped = zip(names, scores)
for item in zipped:
print(item)
# ('Alice', 85)
# ('Bob', 92)
# ('Charlie', 78)
Попробуйте применить zip() к последовательностям разной длины. Проанализируйте результат
zip()
часто используется для создания словарей. В главе, посвященной словарям, мы узнаем, как это работает
enumerate()
Функция
enumerate
возвращает индекс элемента и сам элемент последовательности в качестве кортежа. Вот общий формат функцииenumerate
:
enumerate(последовательность)
Пример использования:
letters = enumerate(['а','б','в'])
list(letters)
[(0, 'а'), (1, 'б'), (2, 'в')]
enumerate()
часто используют в циклеfor
, когда нам нужен и элемент и его индекс:
names = ['Alice', 'Bob', 'John', 'Elena']
for i, name in enumerate(names):
print(f'{name} имеет индекс {i}')
Практика:
Напишите функцию, которая принимает на вход список чисел и возвращает новый список, состоящий из чисел, имеющих четный индекс. Используйте
enumerate()
Last updated