> For the complete documentation index, see [llms.txt](https://timosii.gitbook.io/py_tutorial/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://timosii.gitbook.io/py_tutorial/funkcii/funkcionalnoe-programmirovanie.md).

# Функциональное программирование

### Введение

Функциональное программирование — это такая методика написания программ, когда **в центре внимания находятся функции**. Функции могут присваиваться переменным, они могут передаваться в другие функции и порождать новые функции. Это можно назвать **стилем** или **парадигмой** программирования

В этой парадигме широко используются **функции высшего порядка** и **анонимные функции**

**Функции высшего порядка (ФВП)** — это функции, которые принимают в качестве аргумента другие функции и/или возвращают функции в качестве результата. Мы коснулись их в прошлом уроке, а в этой главе познакомимся поближе

**Анонимные функции** (lambda-функции) — безымянные функции

### Анонимные функции (lambda)

**Вспомним:**&#x20;

* Функции — это небольшие подпрограммы внутри основного кода. При запуске они могут взять какие-то стартовые **параметры**, обработать их, получить свой результат и **отдать этот результат** в основной код
* Чтобы вызвать функцию, нужно указать её имя, но бывают такие функции, у которых нет имени, но их всё равно можно вызывать

*Пример обычной функции:*

```python
def add_func(x, y):
    result = x + y
    return result
print(add_func(2,3))
```

* У функции есть имя, тело функции и есть результат, который она возвращает. Чтобы вызвать эту функцию, мы указываем имя  и аргументы, которые нужно обработать.
* Мы можем значительно усложнить функцию, для этого добавить в неё нужные инструкции

**Лямбда-функции** выглядят по-другому — они не имеют имени, но у них есть ключевое слово **lambda**.

* **lambda** *аргументы*: *выражение (значение функции)*

```python
result = lambda x, y: x + y
print(result(2, 3))
```

* лямбда-функция **всегда возвращает значение**
* она должна быть **записана в одну строку**
* внутри функций нет присваивания и сложной логики — это всегда **простое выражение**

#### Зачем нужны лямбда-функции?

* Самое частое применение — в качестве аргументов в других функциях
* ***Пример:***

{% code overflow="wrap" %}

```python
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]
```

{% endcode %}

* Пойдём дальше — мы можем вместо `filter_numbers` тоже использовать лямбда-функцию:

{% code overflow="wrap" %}

```python
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]
```

{% endcode %}

* Технически, можно обойтись без лямбда-функций. Но иногда с ними получается удобнее

***Практика:***

* Напишите четыре анонимные функции. Они должны:
  * Возводить переданное число в квадрат
  * Умножать два переданных числа
  * Возвращать бОльшее из двух переданных чисел
  * Возвращать первую букву переданного слова
* Выведите на экран результат работы этих функций

### Встроенные ФВП: map() filter() reduce()

#### map()

* Допустим, мы хотим применить к каждому элементу списка какую-то функцию или операцию. Мы можем сделать при помощи цикла for:

```python
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]
```

* Можем определить две функции, одна из которых принимает последовательность и функцию, а вторая является той функцией, которую нужно применить:

```python
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()**
* Она принимает в качестве аргументов функцию и последовательность. И применяет переданную функцию к каждому элементу последовательности&#x20;

{% code overflow="wrap" %}

```python
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]
```

{% endcode %}

* Часто вместе в качестве аргументов для ФВП используют анонимные функции:

```python
numbers = [1, 2, 3, 4, 5]
print(list(map(lambda n: n ** 2 - 10, numbers)))
# [-9, -6, -1, 6, 15]
```

#### filter()

```python
filter(function or None, iterable) -> filter object
```

* Как мы видим из сигнатуры, функция принимает функцию или None и итерируемую последовательность. Возвращает объект `filter`

```python
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`

```python
from functools import reduce

reduce(function, iterable[, initializer]) -> value
```

* Функция `reduce()` кумулятивно применяет функцию `function` к элементам итерируемой `iterable` последовательности, сводя её к единственному значению
* По сути результат работы `reduce()` это аккумулятор

```python
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` соответственно. Это не готовые последовательности, а **итераторы** — интерфейсы для взаимодействия с последовательностями, но не сами последовательности.&#x20;
* Мы можем использовать их как `iterable` объекты, но не можем увидеть всю последовательность. &#x20;
* Сама последовательность *скрыта* и данные предоставляются "по запросу" — то есть только тогда, когда это потребуется
* Это позволяет эффективнее использовать память, чем при хранении, например, списка
* Если нам нужна сама последовательность, результат работы `map() filter() reduce()` нужно преобразовать в неё

```python
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()`, чтобы объединить отфильтрованные строки в одну строку через запятую.
  * Верните результат.
* Для тестирования используйте следующие данные:

```python
['I love Python', 'You love Python', 'We love Python', "I don't love anything"]
Подстрока: 'PYTHON'
```

### Некоторые полезные функции

#### zip()

* Функция `zip()` — объединяет отдельные элементы нескольких последовательностей в кортежи. Возвращает объект `zip`

```python
zip(последовательность, последовательность, ...)
```

* Примеры использования:

```python
x = 'абв'
y = 'эюя'
zipped = zip(x, y)
list(zipped)
# [('а', 'э'), ('б', 'ю'), ('в', 'я')]
```

```python
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`:

```python
enumerate(последовательность)
```

* Пример использования:

```python
letters = enumerate(['а','б','в'])
list(letters)
[(0, 'а'), (1, 'б'), (2, 'в')]
```

* `enumerate()` часто используют в цикле `for`, когда нам нужен и элемент и его индекс:

```python
names = ['Alice', 'Bob', 'John', 'Elena']
for i, name in enumerate(names):
    print(f'{name} имеет индекс {i}')
```

***Практика:***

* Напишите функцию, которая принимает на вход список чисел и возвращает новый список, состоящий из чисел, имеющих четный индекс. Используйте `enumerate()`
