Функциональное программирование
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