# Функции. Продолжение

### Распаковка и упаковка аргументов

* Функции могут принимать неопределенное количество аргументов.
* Для того, чтобы функция приняла и обработала все переданные позиционные аргументы, используется синтаксис `*args:`

```python
def sum_all(*args):
    count = 0
    for n in args: # args это кортеж из переданных аргументов
        count += n
    return count
```

***Практика:*** напишите функцию, которая перемножает все переданные ей аргументы

* Это была упаковка аргументов. А так выглядит распаковка:

```python
def greet(name, age):
    print(f"Привет, {name}! Тебе {age} лет.")

person_info = ('Анна', 25) # это тип данных кортеж, содержит 2 элемента
greet(*person_info)  # Распаковываем кортеж и передаем его как два аргумента
```

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

### Параметры по умолчанию

* Параметры по умолчанию позволяют задать значения по умолчанию для аргументов функции. Если аргумент не передан при вызове функции, то будет использовано значение по умолчанию.

***Пример:***

```python
def greet(name, age=30):
    print(f"Привет, {name}! Тебе {age} лет.")

greet("Максим")  # Если не передан второй аргумент, age будет равно 30
```

В этом примере `age=30` устанавливает значение по умолчанию для аргумента `age`. Если `age` не передан при вызове, то будет использоваться 30.

***Практика:*** определите функцию *squaring* (возведение в квадрат), которая имеет два параметра — число *num* и степень *degree,* которая по умолчанию равна 2.

### Функции как объекты первого класса

* В Python функции являются объектами первого класса. Это означает, что их можно передавать в качестве аргументов другим функциям, возвращать как значения других функций и хранить в переменных или структурах данных как любой другой объект.&#x20;

```python
def hello(name):
    print(f"Hello, {name}!")
    
greeting_func = hello
```

```python
def apply(func, value):
    return func(value)

def double(x):
    return x * 2

result = apply(double, 5)
```

* Рассмотрим другой пример с передачей функции в качестве аргумента другой функции:

```python
def apply_function(numbers, function):
    results = []
    for number in numbers:
        result = function(number)
        results.append(result)
    return results
    
def square(number):
    return number ** 2
    
numbers = [1, 2, 3, 4, 5]
squared_numbers = apply_function(numbers, square)
```

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

* Создайте функцию `filter_numbers(numbers, filter_func)`, которая принимает список чисел `numbers` и функцию `filter_func`. Функция `filter_numbers` должна вернуть новый список, содержащий только те числа из исходного списка, для которых `filter_func` возвращает `True`
* Пример использования:

```python
def is_even(x):
    return x % 2 == 0

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered_numbers = filter_numbers(numbers, is_even)
print(filtered_numbers)
# [2, 4, 6, 8, 10]
```

### Рекурсия

* Рекурсия — определение, описание, изображение какого-либо объекта или процесса внутри самого этого объекта или процесса, то есть ситуация, когда объект является частью самого себя.
* "Чтобы понять рекурсию, надо понять рекурсию"

<figure><img src="/files/R2dbEFkZFUjXEERqNNXR" alt=""><figcaption><p>Рекурсивное изображение экрана</p></figcaption></figure>

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

{% code overflow="wrap" %}

```python
def sum_range(n):
    if n == 1:
        return 1    
    return sum_range(n - 1) + n

# Что происходит? Функции при вызове внутри себя попадают в стек вызовов - по принципу стопки монет
# Как только функция достигнет крайнего случая - стек вызовов начнет выполнятся
# sum_range(5) - в стек
# sum_range(4) - в стек
# sum_range(3) - в стек
# sum_range(2) - в стек
# sum_range(1) - 1 - выполняется, т.к. мы предусмотрели крайний случай
# sum_range(2) - 3 - из стека и так далее
# sum_range(3) - 6
# sum_range(4) - 10
# sum_range(5) - 15
```

{% endcode %}

```python
def matryoshka(n):
    if n == 1:
        print("Матрёшечка")
    else:
        print("Верх матрёшки n=", n)
        matryoshka(n - 1)
        print("Низ матрёшки n=", n)
```

* Посмотрите на примеры функций. Попробуйте запустить код, проанализируйте результат

```python
def print_numbers(n):
    if n > 0:
        print_numbers(n - 1)
        print(n)
```

```python
def sum_list(lst):
    if not lst:
        return 0
    else:
        return lst[0] + sum_list(lst[1:])
```

***Практика:***&#x20;

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://timosii.gitbook.io/py_tutorial/funkcii/funkcii-2.0.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
