Функции в Python. Создание собственных функций.

Функции в Python

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

Содержание
  1. Для чего нужны функции?
  2. Примеры функций.
  3. Функции калькулятора
  4. Как создать функцию в Python
  5. Вызов функции:
  6. Пример вызова функции:
  7. Допустимые имена функций:
  8. Ключевое слово return
  9. Возврат функцией сразу нескольких значений.
  10. Параметры функции
  11. Функции с различным числом параметров.
  12. Функции с переменным числом именованных и неименованных параметров.
  13. Переменное число именованных (ключевых) параметров:
  14. Явное указание аргументов при вызове функции
  15. Обязательные и необязательные аргументы. Значения аргументов по-умолчанию
  16. Аннотации типов (type hints)
  17. Union в аннотациях
  18. Локальные и глобальные переменные.
  19. Определение локальной и глобальной переменной
  20. Глобальная и локальная переменная с одинаковым именем.
  21. Задания на функции Python
  22. Функции как переменные (объекты)
  23. Практические примеры
  24. Динамический выбор функции в зависимости от условия:
  25. Динамическая сортировка данных:
  26. Роутинг в веб-приложениях:
  27. Функция как аргумент другой функции
  28. Пример улучшения гибкости программы за счет функций-аргументов
  29. Лямбда функции
  30. Рекурсия
  31. Принцип работы рекурсивной функции
  32. Пример базовых и рекурсивных случаев в рекурсивной функции
  33. Как избежать бесконечной рекурсии
  34. Стек вызовов функций
  35. Пример стека рекурсивной функции на примере вычисления факториала
  36. Вложенные функции
  37. Пример реализации
  38. Организация замыкания в Python
  39. Локальные функции
  40. nonlocal переменные
  41. Декораторы
  42. Синтаксис декоратора
  43. Практическое применение декораторов
  44. Логирование
  45. Кэширование
  46. Декораторы с параметрами
  47. Пример реализации декоратора с параметром.
  48. Модуль functools
  49. Практическое применение functools
  50. Кэширование для ускорения рекурсивных вычислений
  51. Обертывание функции с фиксированными аргументами
  52. Декоратор для проверки типов аргументов

Для чего нужны функции?

Здесь и далее, в примерах кода использованы символы кириллицы. Это сделано для лучшего пояснения логики работы функций. Я не рекомендую использовать идентификаторы на кириллице  в своем коде на практике. Используйте латинские буквы.

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

# Повторяющийся код
зарплата_сотрудника1 = 5000
зарплата_сотрудника2 = 6000
зарплата_сотрудника3 = 5500

годовой_доход_сотрудника1 = зарплата_сотрудника1 * 12
годовой_доход_сотрудника2 = зарплата_сотрудника2 * 12
годовой_доход_сотрудника3 = зарплата_сотрудника3 * 12

print("Годовой доход сотрудника 1:", годовой_доход_сотрудника1)
print("Годовой доход сотрудника 2:", годовой_доход_сотрудника2)
print("Годовой доход сотрудника 3:", годовой_доход_сотрудника3)

В этом коде мы повторяем одну и ту же операцию для каждого сотрудника: умножаем ежемесячную зарплату на 12, чтобы рассчитать годовой доход. Этот код повторяется для каждого сотрудника, и это неэффективно и трудно поддерживать.

Теперь давайте покажем, как реализовать функцию, чтобы упаковать этот повторяющийся код:

# Функция для расчета годового дохода
def расчет_годового_дохода(ежемесячная_зарплата):
    return ежемесячная_зарплата * 12

# Зарплаты сотрудников
зарплата_сотрудника1 = 5000
зарплата_сотрудника2 = 6000
зарплата_сотрудника3 = 5500

# Рассчитываем годовой доход с использованием функции
годовой_доход_сотрудника1 = расчет_годового_дохода(зарплата_сотрудника1)
годовой_доход_сотрудника2 = расчет_годового_дохода(зарплата_сотрудника2)
годовой_доход_сотрудника3 = расчет_годового_дохода(зарплата_сотрудника3)

print("Годовой доход сотрудника 1:", годовой_доход_сотрудника1)
print("Годовой доход сотрудника 2:", годовой_доход_сотрудника2)
print("Годовой доход сотрудника 3:", годовой_доход_сотрудника3)

С использованием функции расчет_годового_дохода мы можем упаковать операцию расчета годового дохода в одну функцию и повторно использовать ее для каждого сотрудника, что делает код более читаемым и поддерживаемым.

Благодаря функциям, мы:

  • избегаем дублирования кода;
  • уменьшаем шанс ошибок при изменении расчетов;
  • ускоряем процесс написания программы;

Примеры функций.

  • print(): используется для вывода текста или значений на экран.
  • len(): используется для определения длины строки, списка или другой структуры данных.

Эти функции уже встроены в Python, и мы можем использовать их без необходимости их создавать. Но мы можем создать и собственные функции. Они позволяют нам определить собственные операции и повторно использовать код.

Функции калькулятора

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

def add(x, y):
    return x + y

def subtract(x, y):
    return x - y

def multiply(x, y):
    return x * y

def divide(x, y):
    if y == 0:
        return "Ошибка: деление на ноль"
    return x / y

# Пример использования функций:
result = add(5, 3)  # Результат: 8

Как создать функцию в Python

Для создания функции в Python используется ключевое слово def, за которым следует имя функции, список параметров в круглых скобках и двоеточие. Затем идет блок кода, который будет выполняться при вызове функции. Синтаксис выглядит следующим образом:

def имя_функции(параметр1, параметр2, ...):
    # Блок кода функции
    # ...
    return результат
  • имя_функции — имя, которое вы выбираете для вашей функции. Имена функций должны быть осмысленными и описывать, что функция делает.
  • параметр1, параметр2, ... — список параметров, которые функция принимает. Параметры являются значениями, которые передаются функции для выполнения операций.
  • : — двоеточие обязательно используется для указания начала блока кода функции.
  • return результат — ключевое слово return используется для возврата значения из функции. Это не обязательно, и функция может и не возвращать результат.
def hello(имя):
    return f"Привет, {имя}!"

Вызов функции:

Для вызова функции используется ее имя, а также передаются значения аргументов (если они были определены в параметрах функции) внутри круглых скобок. Результат выполнения функции может быть сохранен в переменной или использован непосредственно в программе.

Пример вызова функции:

привет = hello("Анна")
print(привет)  # Результат: "Привет, Анна!"

Допустимые имена функций:

При выборе имени функции в Python существуют следующие правила и соглашения:

  1. Имя функции должно начинаться с буквы (a-z, A-Z) или символа подчеркивания (_).
  2. После первой буквы можно использовать буквы, цифры и символ подчеркивания.
  3. Имена функций чувствительны к регистру, то есть «my_function» и «My_Function» считаются разными функциями.
  4. Нельзя использовать зарезервированные слова (как, например, «if», «while», «return») в качестве имени функции.

Примеры допустимых имен функций:

  • calculate_total
  • _private_function
  • open_file
  • print_sum

Ключевое слово return

return — это ключевое слово в Python, которое используется внутри функций для возврата результата или значения из функции обратно в вызывающий код. Оно является необязательным, и функция может существовать без него.

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

def add(x, y):
    result = x + y
    return result

sum_result = add(3, 5)  # Вызов функции и получение результата
print(sum_result)  # Результат: 8

В этом примере функция add принимает два аргумента x и y, выполняет операцию сложения и возвращает результат с помощью return. Результат сохраняется в переменной sum_result, и мы можем его вывести на экран.

Когда использовать return:

  • return используется, когда функция должна вернуть какое-либо значение или результат своей работы.
  • Это позволяет сохранить результат выполнения функции и использовать его в вызывающем коде для дальнейших операций.
  • Если return не используется в функции, она всё равно выполняется, но она не возвращает никакого значения, и её результат будет None (ничего).

Пример без return:

def greet(name):
    print(f"Привет, {name}!")

greet("Анна")

В этом примере функция greet просто выводит приветствие на экран, но она не возвращает никакого значения. Если бы мы попытались сохранить результат её вызова в переменной, эта переменная была бы равна None.

Когда в функции используется ключевое слово return, функция завершает свою работу и возвращает значение вызывающему коду. Это означает, что после выполнения инструкции return, выполнение функции завершается, и выполнение кода продолжается в том месте, где была вызвана функция.

Возврат функцией сразу нескольких значений.

Функция в Python может вернуть несколько значений, используя механизм кортежей или списков. Это достигается путем упаковки значений в кортеж или список и возврата этого кортежа или списка. После этого вызывающий код может распаковать возвращенное значение и использовать отдельные элементы.

Пример с возвратом нескольких значений:

def oper(a, b):
    сумма = a + b
    разность = a - b
    произведение = a * b
    частное = a / b
    return сумма, разность, произведение, частное

результат = oper(10, 2)
print(результат)  # Результат: (12, 8, 20, 5.0)

В этом примере функция oper() принимает два аргумента a и b, выполняет несколько операций и возвращает кортеж, содержащий результаты этих операций. Возвращенное значение сохраняется в переменной результат. Чтобы получить доступ к отдельным значениям в кортеже, можно использовать индексацию:

сумма, разность, произведение, частное = результат
print("Сумма:", сумма)  # Результат: Сумма: 12
print("Разность:", разность)  # Результат: Разность: 8
print("Произведение:", произведение)  # Результат: Произведение: 20
print("Частное:", частное)  # Результат: Частное: 5.0

Вы также можете использовать индексацию, чтобы получить доступ к отдельным элементам кортежа напрямую, например, результат[0], результат[1] и так далее.

Помимо кортежей, вы можете использовать списки или даже создавать собственные классы, чтобы вернуть несколько значений из функции в Python. Однако кортежи наиболее часто используются в этой роли из-за своей неизменяемости и удобства.

Задание 1. Напишите функцию calculate_average, которая принимает список чисел в качестве аргумента и возвращает среднее арифметическое этих чисел.

Решение
def calculate_average(numbers):
    if len(numbers) == 0:
        return 0  # Возвращаем 0, если список пустой, чтобы избежать деления на ноль
    total = sum(numbers)  # Суммируем все числа в списке
    average = total / len(numbers)  # Рассчитываем среднее значение
    return average

# Пример использования функции:
numbers = [5, 10, 15, 20, 25]
avg = calculate_average(numbers)
print("Среднее значение:", avg)

Функция calculate_average принимает список numbers в качестве аргумента. Если список не пустой, она вычисляет сумму чисел с помощью функции sum() и затем делит эту сумму на количество чисел в списке, чтобы получить среднее значение. Среднее значение затем возвращается из функции.

Задание 2. Напишите функцию print_square, которая принимает число в качестве аргумента и выводит на экран его квадрат, но ничего не возвращает.

Решение
def print_square(number):
    square = number ** 2
    print(f"Квадрат числа {number} равен {square}")

# Пример использования функции:
print_square(5)

Функция print_square принимает число number в качестве аргумента, вычисляет его квадрат с помощью оператора **, и затем выводит результат на экран с использованием функции print(). В этой функции нет оператора return, так как она не возвращает значение, а просто выполняет действие — вывод на экран.

Параметры функции

Параметры функции — это переменные, которые объявляются в сигнатуре (заголовке) функции и используются для передачи значений внутрь функции при её вызове.

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

Значение параметров функции — это конкретные данные или значения, которые передаются функции в качестве аргументов при её вызове.

Значения этих параметров могут изменяться при каждом вызове функции, что делает функции универсальными и многократно используемыми.

Пример объявления функции с параметрами:

def приветствие(имя):
    print(f"Привет, {имя}!")

В этой функции имя — это параметр. Когда функция вызывается, значение параметра передается в функцию. Например:

приветствие("Анна")

В этом вызове функции "Анна" — это значение параметра имя.

Значение параметров позволяет функции работать с конкретными данными, что делает её более гибкой и полезной. Внутри функции можно использовать переданные параметры для выполнения различных операций и возврата результатов с использованием ключевого слова return.

Функции с различным числом параметров.

Функция для вычисления среднего арифметического из двух чисел:

def average_of_two_numbers(num1, num2):
    total = num1 + num2
    average = total / 2
    return average

result = average_of_two_numbers(10, 20)
print("Среднее арифметическое двух чисел:", result)

В этой функции average_of_two_numbers есть два параметра num1 и num2, которые представляют два числа, для которых вычисляется среднее арифметическое.

Функция для вычисления среднего арифметического из трех чисел:

def average_of_three_numbers(num1, num2, num3):
    total = num1 + num2 + num3
    average = total / 3
    return average

result = average_of_three_numbers(10, 20, 30)
print("Среднее арифметическое трех чисел:", result)

В этой функции average_of_three_numbers есть три параметра num1, num2 и num3, представляющие три числа.

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

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

def average_of_numbers(*numbers):
    total = sum(numbers)
    average = total / len(numbers)
    return average

result = average_of_numbers(10, 20, 30, 40, 50)
print("Среднее арифметическое произвольного числа чисел:", result)

В этой функции average_of_numbers параметр *numbers позволяет передавать произвольное количество чисел. Функция суммирует все числа, используя sum(), и затем делит на их количество, чтобы вычислить среднее арифметическое.

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

Функции с переменным числом именованных и неименованных параметров.

В Python вы можете создавать функции с переменным числом параметров как неименованных (позиционных), так и именованных (ключевых). Это делает функции более гибкими и позволяет им принимать разное количество аргументов при вызове.

Переменное число неименованных (позиционных) параметров:

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

Пример:

def суммировать(*args):
    total = sum(args)
    return total

результат = суммировать(1, 2, 3, 4, 5)
print(результат)  # Результат: 15

В этом примере параметр *args позволяет функции суммировать принимать произвольное количество чисел, переданных при вызове, и суммировать их.

Переменное число именованных (ключевых) параметров:

Для создания функции с переменным числом именованных параметров используется два символа ** перед именем параметра в описании функции. Это позволяет функции принимать произвольное количество аргументов в форме пар ключ-значение (словарь).

Пример:

def информация_о_пользователе(**kwargs):
    for ключ, значение in kwargs.items():
        print(f"{ключ}: {значение}")

информация_о_пользователе(имя="Анна", возраст=30, город="Москва")

В этом примере параметр **kwargs позволяет функции информация_о_пользователе принимать произвольное количество именованных параметров, переданных в виде пар ключ-значение (например, «имя»=»Анна», «возраст»=30).

Использование комбинированных параметров:

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

def комбинированная_функция(arg1, arg2, *args, **kwargs):
    print(f"arg1: {arg1}")
    print(f"arg2: {arg2}")
    print(f"Дополнительные аргументы: {args}")
    print(f"Именованные аргументы: {kwargs}")

комбинированная_функция(1, 2, 3, 4, 5, имя="Анна", возраст=30)

В этом примере arg1 и arg2 — это обязательные неименованные параметры, *args — необязательные неименованные параметры, и **kwargs — необязательные именованные параметры.

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

Явное указание аргументов при вызове функции

При вызове функций в Python вы можете явно указать названия аргументов, используя синтаксис «имя_аргумента=значение». Это называется передачей аргументов по ключевым словам (keyword arguments). Этот подход полезен, когда у функции есть множество параметров, и вы хотите явно указать, какое значение передается какому параметру, даже если порядок параметров не соответствует их объявлению в сигнатуре функции.

Пример:

def приветствие(имя, возраст):
    print(f"Привет, {имя}! Тебе {возраст} лет.")

# Вызов функции с явным указанием аргументов по ключевым словам:
приветствие(имя="Анна", возраст=30)

В этом примере мы вызываем функцию приветствие и явно указываем значения для параметров имя и возраст с использованием их имен как ключевых слов. Это позволяет передавать аргументы в любом порядке и делает код более читаемым, так как сразу видно, какой аргумент какому параметру соответствует.

При использовании ключевых слов можно также передавать только часть аргументов, оставив остальные с значениями по умолчанию, если они заданы в сигнатуре (описании) функции.

Обязательные и необязательные аргументы. Значения аргументов по-умолчанию

В Python значения аргументов по умолчанию указываются в сигнатуре (заголовке) функции. Значения по умолчанию используются для аргументов, которые могут быть опущены при вызове функции, и если они не переданы, то будут использоваться значения по умолчанию. Вот как это делается:

def функция(аргумент1, аргумент2=значение_по_умолчанию):
    # Тело функции
  • аргумент1 — это обязательный аргумент, который должен быть передан при вызове функции.
  • аргумент2 — это аргумент с значением по умолчанию. Если он не передан при вызове функции, будет использоваться значение_по_умолчанию.

Пример:

def приветствие(имя, возраст=30):
    print(f"Привет, {имя}! Тебе {возраст} лет.")

# Вызов функции без указания значения для возраста:
приветствие("Анна")  # Результат: Привет, Анна! Тебе 30 лет.

# Вызов функции с указанием значения для возраста:
приветствие("Петр", 25)  # Результат: Привет, Петр! Тебе 25 лет.

В этом примере возраст имеет значение по умолчанию 30, но мы можем явно указать другое значение при вызове функции.

Таким образом, аргументы без значений по умолчанию считаются обязательными, и их необходимо передавать при вызове функции. Аргументы с значениями по умолчанию являются необязательными, и их можно опустить при вызове, используя значения по умолчанию.

Обязательные аргументы должны идти перед аргументами с значениями по умолчанию в описании функции!

Аннотации типов (type hints)

Аннотации типов в Python позволяют указывать типы данных переменных, аргументов функций и возвращаемых значений. Эти аннотации являются частью синтаксиса Python 3, введенного в PEP 484 (Type Hints). Хотя интерпретатор Python сам по себе не выполняет статическую проверку типов, аннотации типов могут быть использованы сторонними инструментами, такими как Mypy, для статической проверки типов в коде.

Аннотации типов в функциях:

def пример_функции(аргумент_1: int, аргумент_2: str) -> float:
    """
    Пример функции с аннотациями типов.
    
    :param аргумент_1: Целое число
    :param аргумент_2: Строка
    :return: Число с плавающей точкой
    """
    результат: float = float(аргумент_1)
    return результат

В этом примере:

  • аргумент_1 имеет аннотацию типа int
  • аргумент_2 имеет аннотацию типа str
  • -> float указывает, что функция должна вернуть значение типа float
  • результат имеет аннотацию типа float

Аннотации типов в классах:

class ПримерКласса:
    def __init__(self, поле_1: int, поле_2: str):
        """
        Конструктор класса с аннотациями типов.

        :param поле_1: Целое число
        :param поле_2: Строка
        """
        self.поле_1: int = поле_1
        self.поле_2: str = поле_2

    def метод(self, аргумент: float) -> str:
        """
        Пример метода класса с аннотациями типов.

        :param аргумент: Число с плавающей точкой
        :return: Строка
        """
        return str(self.поле_1 + аргумент)

В этом примере:

  • поле_1 и поле_2 имеют аннотации типов int и str соответственно.
  • аргумент метода метод имеет аннотацию типа float.
  • -> str указывает, что метод должен вернуть значение типа str.

Эти аннотации не обязательны и не влияют на выполнение программы. Они служат для предоставления информации о типах и могут быть использованы инструментами статической проверки типов. Также, они могут служить документацией для кода, предоставляя читателям информацию о том, какие типы данных ожидаются и возвращаются функциями и методами.

Union в аннотациях

Аннотации типов могут быть использованы совместно с оператором Union из модуля typing для указания нескольких возможных типов для переменной, аргумента или возвращаемого значения.

Пример использования аннотаций типов с Union:

from typing import Union

def объединенная_функция(переменная: Union[int, float, str]) -> Union[float, str]:
    if isinstance(переменная, int):
        # Если переменная - целое число, возвращаем его в виде числа с плавающей точкой
        return float(переменная)
    elif isinstance(переменная, float):
        # Если переменная - число с плавающей точкой, возвращаем ее в виде строки
        return str(переменная)
    else:
        # Если переменная - строка, добавляем к ней строку " - это строка"
        return переменная + " - это строка"

# Пример использования
результат_1 = объединенная_функция(42)      # float
результат_2 = объединенная_функция(3.14)    # str
результат_3 = объединенная_функция("Hello") # str

В этом примере переменная переменная может быть либо целым числом, либо числом с плавающей точкой, либо строкой. Тип возвращаемого значения функции также указан как Union[float, str], что означает, что функция может вернуть значение типа float или str.

Задание 3. Напишите функцию сумма_двух_чисел, которая принимает два аргумента и возвращает их сумму.

Решение
def сумма_двух_чисел(a, b):
    return a + b

результат = сумма_двух_чисел(5, 7)
print("Сумма двух чисел:", результат)

В этой задаче функция сумма_двух_чисел принимает два аргумента и возвращает их сумму. Количество аргументов жестко задано — всегда два.

Задание 4. Напишите функцию сумма_произвольных_чисел, которая принимает произвольное количество чисел и возвращает их сумму.

Решение
def сумма_произвольных_чисел(*args):
    return sum(args)

результат = сумма_произвольных_чисел(5, 7, 10, 15)
print("Сумма произвольных чисел:", результат)

В этой задаче функция сумма_произвольных_чисел принимает произвольное количество чисел, используя параметр *args, и возвращает их сумму.

Задание 5. Напишите функцию приветствие, которая принимает имя и возраст, где возраст является необязательным аргументом со значением по умолчанию.

Решение
def приветствие(имя, возраст=30):
    print(f"Привет, {имя}! Тебе {возраст} лет.")

приветствие("Анна")  # Результат: Привет, Анна! Тебе 30 лет.
приветствие("Петр", 25)  # Результат: Привет, Петр! Тебе 25 лет.

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

Задание 6. Напишите функцию произведение, которая принимает произвольное количество чисел и возвращает их произведение.

Решение
def произведение(*args):
    результат = 1
    for число in args:
        результат *= число
    return результат

результат = произведение(2, 3, 4)
print("Произведение чисел:", результат)

В этой задаче функция произведение принимает произвольное количество чисел, используя параметр *args, и возвращает их произведение.

Задание 7. Напишите функцию информация_о_студенте, которая принимает имя студента и произвольное количество дополнительных именованных аргументов, таких как «возраст», «факультет», и выводит информацию о студенте.

Решение
def информация_о_студенте(имя, **kwargs):
    print(f"Имя студента: {имя}")
    for ключ, значение in kwargs.items():
        print(f"{ключ}: {значение}")

информация_о_студенте("Анна", возраст=20, факультет="Информатика", группа="ИИ-101")

В этой задаче функция информация_о_студенте принимает имя студента как обязательный аргумент и произвольное количество именованных аргументов (ключ-значение), которые выводятся как информация о студенте.

Локальные и глобальные переменные.

Рассмотрим пример.

var = 10

def функция():
    print("Значение глобальной переменной внутри функции:", var)

функция()
print("Значение глобальной переменной после вызова функции:", var)

Результат

Значение глобальной переменной внутри функции: 10
Значение глобальной переменной после вызова функции: 10

В этом примере функция функция использует переменную var внутри себя.

Немного поменяем программу (пример2):

def функция():
    var = 20

функция()
print(var)  # Это вызовет ошибку, так как переменная недоступна вне функции

В этом примере переменная var объявлена внутри функции и является локальной к этой функции. Она недоступна вне функции, поэтому вызов print(локальная_переменная) приведет к ошибке.

Еще один пример(3).

var = 30  # Глобальная переменная

def функция():
    var = 40  # Локальная переменная с тем же именем
    print("Значение переменной внутри функции:", var)

функция()
print("Значение переменной после вызова функции:", var)

Результат:

Значение переменной внутри функции: 40
Значение переменной после вызова функции: 30

Почему два числа различны?

В этом примере функция функция имеет свою собственную локальную переменную var, которая имеет то же имя, что и глобальная переменная var вне функции.

Это две разные переменные.

Внутри функции использована локальная переменная, она сохраняется только на время использования функции, затем удаляется из памяти. А вне функции — глобальная переменная, которая действует на всем протяжении работы программы. Но внутри функции заменяется на локальную!

Еще пример (4).

var = 50  # Глобальная переменная

def функция():
    print(var)  # Попытка использования локальной переменной до ее инициализации
    var = 60

функция()

вызов print(var) приводит к ошибке, так как  есть попытка использования локальной переменной до ее инициализации.

Определение локальной и глобальной переменной

Глобальные переменные и локальные переменные — это два основных типа переменных в программировании, которые определяют область видимости (scope) переменных и их доступность в разных частях кода.

  1. Глобальные переменные:
    • Глобальные переменные объявляются вне всех функций и имеют глобальную область видимости, что означает, что они доступны во всем коде программы, включая функции.
    • Глобальные переменные обычно инициализируются в начале программы и могут использоваться в разных частях кода.
    • Они могут быть изменены в любом месте программы, что может сделать код менее предсказуемым, особенно в больших программах.

Пример глобальной переменной:

глобальная_переменная = 10
  1. Локальные переменные:
    • Локальные переменные объявляются внутри функций и имеют локальную область видимости, ограниченную функцией, в которой они определены.
    • Локальные переменные не видны за пределами функции, в которой они определены. Они существуют только во время выполнения функции.
    • Это помогает изолировать данные и избегать конфликтов имен между функциями.

Пример локальной переменной:

def функция():
    локальная_переменная = 20

Глобальная и локальная переменная с одинаковым именем.

Когда вы объявляете переменную с тем же именем как глобальная переменная внутри функции, она становится локальной переменной, и это может создать проблемы.

Дело в том, что это уже новая переменная, то есть фактически, у вас две переменных: одна глобальная, а вторая локальная, которая действует только внутри функции. См. примеры 3 и 4.

Это сделано специально, для защиты глобальных переменных. Вдруг вы забыли про глобальную переменную и использовали локальную с тем же именем.

Что же делать, если нужно работать с глобальной переменной внутри функции, а не создавать локальную?

Во-первых, максимально избегайте данной ситуации. Например, передавайте значение переменной в качестве аргумента функции.

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

var = 10

def изменить_глобальную_переменную():
    global var # Объявляем переменную как глобальную
    var = 20  # Изменяем глобальную переменную

print("Глобальная переменная до вызова функции:", var)
изменить_глобальную_переменную()
print("Глобальная переменная после вызова функции:", var)

В этом примере global varвнутри функции изменить_глобальную_переменную() объявляет, что мы хотим изменить глобальную переменную, а не создать локальную с тем же именем. Это позволяет функции изменить значение глобальной переменной var.

Результат выполнения:

Глобальная переменная до вызова функции: 10
Глобальная переменная после вызова функции: 20

Обратите внимание, что без global переменная var внутри функции была бы считана как локальная, и это не привело бы к изменению глобальной переменной.

Задание 8.Создайте глобальную переменную счетчик и напишите функцию увеличить_счетчик, которая увеличивает значение счетчика на 1 при каждом вызове.

Решение
счетчик = 0

def увеличить_счетчик():
    global счетчик
    счетчик += 1

увеличить_счетчик()
print(счетчик)  # Результат: 1
увеличить_счетчик()
print(счетчик)  # Результат: 2

В этой задаче счетчик — глобальная переменная, которая изменяется функцией увеличить_счетчик с использованием global.

Задание 9.Напишите функцию сумма_локальная_глобальная, которая принимает два аргумента и возвращает их сумму. Первый аргумент — локальная переменная, второй — глобальная.

Решение
глобальная_переменная = 10

def сумма_локальная_глобальная(локальная):
    глобальная = глобальная_переменная
    return локальная + глобальная

результат = сумма_локальная_глобальная(5)
print(результат)  # Результат: 15

В этой задаче мы передаем локальную переменную 5 и глобальную переменную глобальная_переменная в функцию, где они объединяются для вычисления суммы.

Задание 10.Создайте глобальную переменную имя и две функции: установить_имя для установки значения имя и приветствие для приветствия с использованием этого имени.

Решение
имя = ""

def установить_имя(новое_имя):
    global имя
    имя = новое_имя

def приветствие():
    print(f"Привет, {имя}!")

установить_имя("Анна")
приветствие()  # Результат: Привет, Анна!

В этой задаче глобальная переменная имя используется в двух разных функциях: установить_имя для установки значения и приветствие для вывода сообщения с использованием имени.

Задание 11.Напишите функцию сумма_чисел, которая принимает список чисел и возвращает их сумму, используя локальную переменную total.

Решение
def сумма_чисел(числа):
    total = 0
    for число in числа:
        total += число
    return total

список = [1, 2, 3, 4, 5]
результат = сумма_чисел(список)
print(результат)  # Результат: 15

В этой задаче локальная переменная total используется внутри функции сумма_чисел для накопления суммы чисел из переданного списка.

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

Решение
def возведение_в_степень(число, степень):
    результат = число ** степень
    return результат

результат = возведение_в_степень(2, 3)
print(результат)  # Результат: 8

В этой задаче функция возведение_в_степень принимает два аргумента и возвращает результат без использования глобальных переменных, что является хорошей практикой для избегания побочных эффектов.

Задания на функции Python

Задание 13. Напишите функцию, которая принимает строку и символ в качестве аргументов и возвращает количество раз, которое этот символ встречается в строке.

Решение
def подсчет_символа(строка, символ):
    количество = строка.count(символ)
    return количество

текст = "Пример текста для подсчета символов"
символ_поиска = "о"
кол_во = подсчет_символа(текст, символ_поиска)
print(f"Символ '{символ_поиска}' встречается {кол_во} раз(а) в тексте.")

Задание 14. Напишите функцию, которая принимает строку и определяет, является ли она палиндромом (читается с обеих сторон одинаково).

Решение
def палиндром(строка):
    строка = строка.lower()  # Преобразуем в нижний регистр
    строка = строка.replace(" ", "")  # Убираем пробелы
    обратная_строка = строка[::-1]
    return строка == обратная_строка

слово = "А роза упала на лапу Азора"
результат = палиндром(слово)
print(f"Является ли '{слово}' палиндромом? {результат}")

Задание 15. Напишите функцию, которая генерирует случайный пароль заданной длины из букв и цифр.

Решение
import random
import string

def генератор_пароля(длина):
    символы = string.ascii_letters + string.digits
    пароль = ''.join(random.choice(символы) for _ in range(длина))
    return пароль

длина_пароля = 10
пароль = генератор_пароля(длина_пароля)
print(f"Случайный пароль: {пароль}")

Задание 16. Напишите функцию, которая принимает верхний предел диапазона и возвращает список всех простых чисел в этом диапазоне. Для оптимизации можно использовать алгоритм «Решето Эратосфена».

Решение
def поиск_простых_чисел(верхний_предел):
    решето = [True] * (верхний_предел + 1)
    решето[0] = решето[1] = False
    for число in range(2, int(верхний_предел**0.5) + 1):
        if решето[число]:
            for множитель in range(число * число, верхний_предел + 1, число):
                решето[множитель] = False
    простые_числа = [число for число, is_prime in enumerate(решето) if is_prime]
    return простые_числа

верхний_предел = 100
простые = поиск_простых_чисел(верхний_предел)
print(f"Простые числа в диапазоне до {верхний_предел}: {простые}")

Задание 17. Напишите функцию, которая принимает имя CSV файла и анализирует его содержимое. Файл содержит данные о продажах товаров с колонками «Товар», «Количество» и «Цена». Функция должна вернуть общую сумму продаж и наиболее популярный товар. Можно использовать модуль csv

Решение
import csv

def анализ_данных(имя_файла):
    сумма_продаж = 0
    наиболее_популярный_товар = None
    количество_популярных = 0

    with open(имя_файла, 'r') as файл:
        читатель = csv.DictReader(файл)
        for строка in читатель:
            продажи = int(строка['Количество'])
            сумма_продаж += продажи * float(строка['Цена'])
            товар = строка['Товар']
            if продажи > количество_популярных:
                количество_популярных = продажи
                наиболее_популярный_товар = товар

    return сумма_продаж, наиболее_популярный_товар

файл = 'sales.csv'
общая_сумма, популярный = анализ_данных(файл)
print(f"Общая сумма продаж: {общая_сумма}")
print(f"Наиболее популярный товар: {популярный}")

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

Решение
import random

def генератор_задачи(операция):
    число1 = random.randint(1, 10)
    число2 = random.randint(1, 10)
    if операция == 'сложение':
        ответ = число1 + число2
        оператор = '+'
    elif операция == 'вычитание':
        ответ = число1 - число2
        оператор = '-'
    elif операция == 'умножение':
        ответ = число1 * число2
        оператор = '*'
    elif операция == 'деление':
        ответ = число1 / число2
        оператор = '/'
    задача = f"Решите задачу: {число1} {оператор} {число2} = ?"
    return задача, ответ

операция = 'сложение'
задача, ответ = генератор_задачи(операция)
print(задача)
print(f"Ответ: {ответ}")

Задание 19. Напишите функцию для шифрования и дешифрования текста с использованием шифра Цезаря. Функция должна принимать текст и ключ шифрования, а затем возвращать зашифрованный или расшифрованный текст.

Шифр Цезаря — это вид шифра подстановки, в котором каждый символ в открытом тексте заменяется символом находящимся на некотором постоянном числе позиций левее или правее него в алфавите.

Решение
def шифровать(текст, ключ):
    зашифрованный_текст = ''
    for символ in текст:
        if символ.isalpha():
            сдвиг = ord('a') if символ.islower() else ord('A')
            зашифрованный_символ = chr(((ord(символ) - сдвиг + ключ) % 26) + сдвиг)
            зашифрованный_текст += зашифрованный_символ
        else:
            зашифрованный_текст += символ
    return зашифрованный_текст

def дешифровать(зашифрованный_текст, ключ):
    return шифровать(зашифрованный_текст, -ключ)

текст = "Hello, World!"
ключ = 3
зашифрованный = шифровать(текст, ключ)
print(f"Зашифрованный текст: {зашифрованный}")
расшифрованный = дешифровать(зашифрованный, ключ)
print(f"Расшифрованный текст: {расшифрованный}")

Задание 20. Напишите функцию, которая будет рекурсивно искать файл с заданным именем в указанной директории и всех ее поддиректориях. Функция должна возвращать путь к найденному файлу или сообщение о том, что файл не найден. Используйте модуль os и функции os.walk и os.path.join

Решение
import os

def рекурсивный_поиск_файла(каталог, имя_файла):
    for корень, директории, файлы in os.walk(каталог):
        if имя_файла in файлы:
            return os.path.join(корень, имя_файла)
        for file in директории:
            рекурсивный_поиск_файла(file, имя_файла)
    return "Файл не найден"

директория = '/путь/к/каталогу'
имя_искомого_файла = 'искомый_файл.txt'
результат = рекурсивный_поиск_файла(директория, имя_искомого_файла)
print(f"Путь к файлу: {результат}")

Задание 21. Получить все шестизначные счастливые номера. Счастливым называют такое шестизначное число, в котором сумма его первых трех цифр равна сумме его последних трех цифр. (Определить функцию для расчета суммы цифр трехзначного числа.)

Решение
def сумма_цифр(число):
    сумма = 0
    while число > 0:
        сумма += число % 10
        число //= 10
    return сумма

def найти_счастливые_номера():
    счастливые_номера = []
    for номер in range(100, 1000):
        if сумма_цифр(номер // 100) == сумма_цифр(номер % 100):
            счастливые_номера.append(номер)
    return счастливые_номера

счастливые = найти_счастливые_номера()
print("Счастливые номера:")
for номер in счастливые:
    print(номер)

Задание 22. Дата некоторого дня характеризуется тремя натуральными числами: g (год), m (порядковый номер месяца) и n (число).

По заданным g, n и m определить: а) дату предыдущего дня; б) дату следующего дня.

Определить функцию, подсчитывающую количество дней в том или ином месяце. В обеих задачах рассмотреть два случая: 1) заданный год не является високосным; 2) заданный год может быть високосным

Решение

Для решения этой задачи, нам нужно учесть, что високосные годы имеют некоторые особенности, так как февраль в них может содержать 29 дней.

def количество_дней_в_месяце(год, месяц):
    # Словарь, в котором ключи - номера месяцев, значения - количество дней в месяце
    дни_в_месяце = {
        1: 31,
        2: 29 if (год % 4 == 0 and год % 100 != 0) or (год % 400 == 0) else 28,
        3: 31,
        4: 30,
        5: 31,
        6: 30,
        7: 31,
        8: 31,
        9: 30,
        10: 31,
        11: 30,
        12: 31
    }
    return дни_в_месяце[месяц]

def предыдущий_день(год, месяц, число):
    дни_в_месяце = количество_дней_в_месяце(год, месяц)
    if число > 1:
        return год, месяц, число - 1
    elif месяц > 1:
        return год, месяц - 1, количество_дней_в_месяце(год, месяц - 1)
    else:
        return год - 1, 12, 31

def следующий_день(год, месяц, число):
    дни_в_месяце = количество_дней_в_месяце(год, месяц)
    if число < дни_в_месяце:
        return год, месяц, число + 1
    elif месяц < 12:
        return год, месяц + 1, 1
    else:
        return год + 1, 1, 1

год = 2023
месяц = 9
число = 16

предыдущая_дата = предыдущий_день(год, месяц, число)
следующая_дата = следующий_день(год, месяц, число)

print(f"Дата предыдущего дня: {предыдущая_дата}")
print(f"Дата следующего дня: {следующая_дата}")

Задание 23. Даны натуральные числа a и b, обозначающие соответственно числитель и знаменатель дроби. Сократить дробь, т. е. найти такие натуральные числа p и q, не имеющие общих делителей, что p q a b . (Определить функцию для расчета наибольшего общего делителя двух натуральных чисел, используя алгоритм Евклида.)

Решение

Для сокращения дроби мы можем использовать алгоритм нахождения наибольшего общего делителя (НОД) двух чисел. После нахождения НОД’a числителя и знаменателя дроби, мы делим числитель и знаменатель на НОД, чтобы получить сокращенную дробь.

def наибольший_общий_делитель(a, b):
    while b:
        a, b = b, a % b
    return a

def сократить_дробь(числитель, знаменатель):
    нод = наибольший_общий_делитель(числитель, знаменатель)
    сокращенный_числитель = числитель // нод
    сокращенный_знаменатель = знаменатель // нод
    return сокращенный_числитель, сокращенный_знаменатель

# Пример использования:
a = 24
b = 36

сокращенная_дробь = сократить_дробь(a, b)
print(f"Сокращенная дробь: {сокращенная_дробь[0]}/{сокращенная_дробь[1]}")

Задание 24. Написать функцию, определяющую количество гласных и согласных букв в строке.

Решение
def подсчет_гласных_и_согласных(строка):
    гласные = "aeiouAEIOUаеиоуёяюэыАЕЁИОУЯЮЭЫ"
    количество_гласных = 0
    количество_согласных = 0

    for символ in строка:
        if символ.isalpha():
            if символ in гласные:
                количество_гласных += 1
            else:
                количество_согласных += 1

    return количество_гласных, количество_согласных

# Пример использования:
текст = "Привет, мир!"
гласные, согласные = подсчет_гласных_и_согласных(текст)

print(f"Количество гласных букв: {гласные}")
print(f"Количество согласных букв: {согласные}")

Задание 25. Напишите функцию, определяющую есть ли в тексте запрещенные слова или фразы.

Решение
def есть_запрещенные_слова(текст, запрещенные_слова):
    текст = текст.lower()  # Преобразуем текст в нижний регистр для регистронезависимой проверки
    слова_в_тексте = текст.split()

    for слово in слова_в_тексте:
        if слово in запрещенные_слова:
            return True

    return False

# Пример использования:
текст = "Этот текст содержит запрещенное слово: спам"
запрещенные = ["спам", "мошенничество", "реклама"]

if есть_запрещенные_слова(текст, запрещенные):
    print("Текст содержит запрещенные слова.")
else:
    print("Текст не содержит запрещенных слов.")

Задание 26. Напишите функцию, которая получает список всех натуральных делителей целого числа

Решение
def натуральные_делители(число):
    делители = []
    for i in range(1, число + 1):
        if число % i == 0:
            делители.append(i)
    return делители

# Пример использования:
число = 12
делители = натуральные_делители(число)
print(f"Натуральные делители числа {число}: {делители}")

Задание 27. Напишите функцию, которая возвращает все неуникальные элементы последовательности

Решение
def неуникальные_элементы(последовательность):
    неуникальные = []
    уникальные = set()

    for элемент in последовательность:
        if элемент in уникальные:
            неуникальные.append(элемент)
        else:
            уникальные.add(элемент)

    return неуникальные

# Пример использования:
последовательность = [1, 2, 2, 3, 4, 4, 5]
неуникальные = неуникальные_элементы(последовательность)
print(f"Неуникальные элементы: {неуникальные}")

Функции как переменные (объекты)

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

Вот некоторые ключевые аспекты концепции представления функции как объекта:

Присваивание функций переменным: В Python можно присвоить функцию переменной, что позволяет обращаться к функции через имя этой переменной. Например:

def привет():
    print("Привет, мир!")

моя_функция = привет
моя_функция()  # Вызывает функцию привет

Передача функций как аргументов: Функции можно передавать в качестве аргументов другим функциям. Это используется в функциональном программировании и для создания более гибких и абстрактных функций. Например:

def привет():
    print("Привет, мир!")

def вызвать_функцию(функция):
    функция()

вызвать_функцию(привет)  # Передает функцию привет в качестве аргумента

Создание функций во время выполнения: В Python можно создавать новые функции во время выполнения программы. Это достигается с использованием лямбда-функций или созданием функций внутри других функций.

умножить_на_2 = lambda x: x * 2
результат = умножить_на_2(5)  # Результат: 10

def создать_функцию(коэффициент):
    def функция(x):
        return x * коэффициент
    return функция

умножить_на_3 = создать_функцию(3)
результат = умножить_на_3(5)  # Результат: 15

Возвращение функций из других функций: Функции могут возвращать другие функции в качестве результата. Это позволяет создавать замыкания и более сложные паттерны поведения.

def создать_увеличитель(шаг):
    def увеличить(x):
        return x + шаг
    return увеличить

увеличить_на_5 = создать_увеличитель(5)
результат = увеличить_на_5(10)  # Результат: 15

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

функции = [функция1, функция2, функция3]
for функция in функции:
    функция()  # Вызов каждой функции из списка

Практические примеры

Динамический выбор функции в зависимости от условия:

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

def доставка_по_городу(заказ):
    # Логика доставки по городу
    pass

def доставка_по_региону(заказ):
    # Логика доставки по региону
    pass

def доставка_по_стране(заказ):
    # Логика доставки по стране
    pass

расстояние = 1200  # Расстояние в километрах
if расстояние < 100:
    выбранная_доставка = доставка_по_городу
elif расстояние < 1000:
    выбранная_доставка = доставка_по_региону
else:
    выбранная_доставка = доставка_по_стране

результат = выбранная_доставка(заказ)

Динамическая сортировка данных:

Переменные-функции могут использоваться для изменения порядка сортировки данных в зависимости от потребностей. Например, у нас есть список товаров, и мы хотим сортировать его по разным критериям: по цене, по популярности или по алфавиту.

товары = [
    {"название": "Телефон", "цена": 500, "популярность": 8},
    {"название": "Ноутбук", "цена": 800, "популярность": 9},
    {"название": "Планшет", "цена": 300, "популярность": 7}
]

def сортировка_по_цене(товар):
    return товар["цена"]

def сортировка_по_популярности(товар):
    return товар["популярность"]

def сортировка_по_названию(товар):
    return товар["название"]

критерий_сортировки = сортировка_по_цене
отсортированные_товары = sorted(товары, key=критерий_сортировки)

Роутинг в веб-приложениях:

В веб-фреймворках функции-обработчики маршрутов часто хранятся в словаре, где ключами являются URL-шаблоны, а значениями — соответствующие функции обработки запросов.

def обработчик_главной_страницы():
    # Логика для главной страницы
    pass

def обработчик_страницы_о_нас():
    # Логика для страницы "О нас"
    pass

маршруты = {
    "/": обработчик_главной_страницы,
    "/about": обработчик_страницы_о_нас
}

запрос = получить_запрос()  # Пример получения HTTP-запроса
путь = запрос.путь
функция_обработки = маршруты.get(путь)
if функция_обработки:
    ответ = функция_обработки()
    отправить_ответ(ответ)  # Пример отправки HTTP-ответа

Плагины и расширения в приложениях:

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

class Расширение:
    def выполнить(self, данные):
        pass

классы_расширений = [Расширение1, Расширение2, Расширение3]

расширения = [класс() for класс in классы_расширений]

данные = получить_данные()
for расширение in расширения:
    расширение.выполнить(данные)

Задание 28. Вам нужно создать калькулятор, который принимает два числа и операцию в виде строки (например, «сложение», «вычитание», «умножение», «деление») и выполняет соответствующую операцию. Для реализации этой задачи вы можете использовать словарь, в котором ключами будут строки-операции, а значениями — соответствующие функции.

Решение
def сложение(a, b):
    return a + b

def вычитание(a, b):
    return a - b

def умножение(a, b):
    return a * b

def деление(a, b):
    if b != 0:
        return a / b
    else:
        return "Деление на ноль недопустимо"

операции = {
    "сложение": сложение,
    "вычитание": вычитание,
    "умножение": умножение,
    "деление": деление
}

операция = input("Введите операцию: ")
if операция in операции:
    число1 = float(input("Введите первое число: "))
    число2 = float(input("Введите второе число: "))
    результат = операции[операция](число1, число2)
    print("Результат:", результат)
else:
    print("Неподдерживаемая операция")

Задание 29. Предположим, у вас есть список элементов, и вы хотите фильтровать этот список с помощью различных функций-фильтров. Создайте функции-фильтры, которые будут принимать элемент списка и возвращать True или False в зависимости от условия фильтрации. Затем используйте эти функции для фильтрации списка.

Решение
def фильтр_четных(число):
    return число % 2 == 0

def фильтр_положительных(число):
    return число > 0

def фильтр_длины_меньше_5(строка):
    return len(строка) < 5

список = [1, 2, 3, 4, 5, 6, -1, -2, "abc", "abcdef"]

фильтр = фильтр_четных
отфильтрованный_список = [x for x in список if фильтр(x)]
print("Отфильтрованный список:", отфильтрованный_список)

Задание 30. Разработайте систему плагинов для вашего приложения, которая позволяет добавлять и выполнять дополнительные функции из внешних модулей. Создайте словарь, в котором ключами будут имена плагинов, а значениями — соответствующие функции.

Решение
# Модуль плагина "плагин1.py"
def выполнить_плагин():
    print("Плагин 1 выполнился")

# Модуль плагина "плагин2.py"
def выполнить_плагин():
    print("Плагин 2 выполнился")

плагины = {
    "плагин1": выполнить_плагин,
    "плагин2": выполнить_плагин
}

имя_плагина = input("Введите имя плагина: ")
if имя_плагина in плагины:
    плагины[имя_плагина]()
else:
    print("Плагин не найден")

Функция как аргумент другой функции

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

1. Создание более гибких функций: Передача функции в качестве аргумента позволяет создавать более абстрактные и универсальные функции. Вы можете изменять поведение функции, передавая разные функции в качестве аргументов.

2. Обратные вызовы: Этот механизм часто используется для создания обратных вызовов. Функция, принимающая другую функцию в качестве аргумента, может вызвать эту функцию по необходимости.

Пример передачи функции в качестве аргумента:

Рассмотрим пример сортировки списка. Встроенная функция sorted() принимает аргумент key, который является функцией, определяющей ключ сортировки.

def получить_длину(строка):
    return len(строка)

список_слов = ["яблоко", "груша", "вишня", "апельсин"]

# Сортировка списка по длине слова
отсортированный_список = sorted(список_слов, key=получить_длину)
print(отсортированный_список)

В этом примере функция получить_длину передается как аргумент key функции sorted(). В результате слова сортируются по их длине.

Пример использования функции для фильтрации:

Допустим, у нас есть список чисел, и мы хотим отфильтровать только положительные числа. Мы можем создать функцию для этой фильтрации и передать ее в функцию filter():

def положительное_число(x):
    return x > 0

список_чисел = [-2, 5, -10, 8, 3, -1, 0]

положительные_числа = list(filter(положительное_число, список_чисел))
print(положительные_числа)

Пример улучшения гибкости программы за счет функций-аргументов

Рассмотрим задачу. Допустим, у нас есть список чисел, и мы хотим создать функцию, которая применяет к каждому элементу списка некоторое действие, определяемое пользователем. Мы можем использовать функцию-аргумент для передачи этого действия. Вот пример:

def применить_действие(список, действие):
    """
    Применяет заданное действие ко всем элементам списка и возвращает новый список результатов.
    
    :param список: Список элементов
    :param действие: Функция, которая применяется к элементам списка
    :return: Новый список результатов
    """
    результаты = []
    for элемент in список:
        результат = действие(элемент)
        результаты.append(результат)
    return результаты

# Функция для удвоения числа
def удвоить(x):
    return x * 2

# Функция для возведения числа в квадрат
def возвести_в_квадрат(x):
    return x ** 2

список_чисел = [1, 2, 3, 4, 5]

# Используем применение функции-аргумента
результат1 = применить_действие(список_чисел, удвоить)
результат2 = применить_действие(список_чисел, возвести_в_квадрат)

print("Удвоенные числа:", результат1)
print("Квадраты чисел:", результат2)

В этом примере применить_действие — это более гибкая функция, которая принимает список и функцию-аргумент действие. Эта функция затем применяет переданное действие к каждому элементу списка и возвращает новый список результатов.

Задание 31. Создайте функцию фильтровать_по_длине, которая принимает список строк и функцию-предикат (функцию, которая возвращает булево значение). Функция должна возвращать список строк, которые удовлетворяют условию, заданному функцией-предикатом.

Решение
def фильтровать_по_длине(список, предикат):
    """
    Фильтрует список строк по заданному предикату и возвращает новый список.

    :param список: Список строк
    :param предикат: Функция-предикат, которая определяет условие фильтрации
    :return: Новый список строк, удовлетворяющих условию предиката
    """
    результат = []
    for строка in список:
        if предикат(строка):
            результат.append(строка)
    return результат

# Пример использования:
список_слов = ["яблоко", "груша", "арбуз", "апельсин", "банан"]
длинные_слова = фильтровать_по_длине(список_слов, lambda x: len(x) > 6)
print(длинные_слова)

Задание 32. Создайте функцию вычислить_итог, которая принимает список чисел и функцию-агрегатор (например, функцию для вычисления суммы или произведения) и возвращает результат агрегации элементов списка.

Решение
def вычислить_сумму(список, агрегатор):
    """
    Вычисляет результат агрегации списка чисел с использованием заданной функции-агрегатора.

    :param список: Список чисел
    :param агрегатор: Функция-агрегатор, которая применяется к элементам списка
    :return: Результат агрегации
    """
    результат = агрегатор(список)
    return результат

# Пример использования:
числа = [1, 2, 3, 4, 5]
сумма = вычислить_сумму(числа, sum)
произведение = вычислить_сумму(числа, lambda x: x[0] * x[1])  # Произведение элементов
print("Сумма:", сумма)
print("Произведение:", произведение)

Лямбда функции

Лямбда-функции (или анонимные функции) в Python — это способ создания маленьких и анонимных функций без необходимости объявления их с помощью ключевого слова def.

Лямбда-функции обычно используются там, где требуется короткая функция для выполнения простой операции. Они имеют ограниченный синтаксис и предназначены для использования в одном выражении. Основные характеристики лямбда-функций:

  1. Анонимность: Лямбда-функции не имеют имени, и их можно использовать анонимно в коде.
  2. Краткость: Они представляют собой компактное выражение, часто состоящее из одной строки.
  3. Использование: Лямбда-функции обычно используются в качестве аргументов для других функций, таких как map(), filter(), sorted(), и др.

Синтаксис лямбда-функции выглядит следующим образом:

lambda аргументы: выражение

Примеры лямбда-функций:

# Пример 1: Лямбда-функция для вычисления квадрата числа
квадрат = lambda x: x**2
print(квадрат(5))  # Вывод: 25

# Пример 2: Лямбда-функция для сравнения двух чисел и возврата минимального
минимальное = lambda a, b: a if a < b else b
print(минимальное(10, 15))  # Вывод: 10

Лямбда-функции часто используются вместе с функциями высшего порядка, такими как map(), filter(), и sorted(), для более компактного и читаемого кода:

список_чисел = [1, 2, 3, 4, 5]

# Пример 3: Использование лямбда-функции с map() для вычисления квадратов чисел
квадраты = list(map(lambda x: x**2, список_чисел))
print(квадраты)  # Вывод: [1, 4, 9, 16, 25]

# Пример 4: Использование лямбда-функции с filter() для фильтрации положительных чисел
положительные = list(filter(lambda x: x > 0, список_чисел))
print(положительные)  # Вывод: [1, 2, 3, 4, 5]

Лямбда-функции особенно полезны, когда требуется быстро определить небольшую функцию для обработки данных или фильтрации элементов в списке без необходимости создавать отдельную именованную функцию с помощью def.

Задание 33. Сортировка списка словарей по значению ключа. Пусть у вас есть список словарей, и вы хотите отсортировать его по значению определенного ключа в каждом словаре. Используйте лямбда-функцию для указания ключа сортировки.

Решение
список_словарей = [
    {"имя": "Алиса", "возраст": 25},
    {"имя": "Боб", "возраст": 30},
    {"имя": "Карл", "возраст": 22}
]

# Сортировка по возрасту с использованием лямбда-функции
отсортированный_список = sorted(список_словарей, key=lambda x: x["возраст"])
print(отсортированный_список)

Задание 34. Фильтрация чисел в списке. У вас есть список чисел, и вы хотите отфильтровать только положительные числа с использованием filter() и лямбда-функции.

Решение
список_чисел = [-2, 5, -10, 8, 3, -1, 0]

# Фильтрация положительных чисел с использованием лямбда-функции
положительные_числа = list(filter(lambda x: x > 0, список_чисел))
print(положительные_числа)

Задание 35. Вычисление суммы элементов списка. Создайте функцию, которая принимает список чисел и лямбда-функцию в качестве аргумента для определения, какую операцию выполнить (например, сложение или умножение).

Решение
def вычислить_операцию(список, операция):
    """
    Вычисляет результат операции для списка чисел с использованием лямбда-функции.

    :param список: Список чисел
    :param операция: Лямбда-функция, определяющая операцию над числами
    :return: Результат операции
    """
    результат = операция(список)
    return результат

числа = [1, 2, 3, 4, 5]

# Пример использования: сумма чисел с использованием лямбда-функции
сумма = вычислить_операцию(числа, lambda x: sum(x))
print("Сумма:", сумма)

Рекурсия

Рекурсия

Рекурсия — это концепция в программировании и математике, при которой функция вызывает саму себя как часть своей работы.

То есть, рекурсивная функция решает задачу, разбивая ее на более мелкие подзадачи, которые также решаются с использованием этой же функции. Рекурсия часто применяется в алгоритмах, которые имеют структуру, подобную «деление на подзадачи».

Представьте, что вы хотите найти файл на жестком диске компьютера. Вам может потребоваться выполнить следующие действия:

  1. Откройте начальную директорию (как корень дерева поиска).
  2. Просмотрите все файлы и поддиректории в текущей директории.
  3. Если вы находите поддиректорию, переходите в нее и повторяете шаги 1-2.
  4. Если вы находите файл, проверьте, соответствует ли он вашему запросу.
  5. Если файл не соответствует запросу, вернитесь к предыдущей директории (шаг назад).
  6. Повторяйте этот процесс, пока не найдете файл или не пройдете все директории.

Этот процесс аналогичен рекурсивной функции. Вы выполняете поиск в текущей директории, а если вы находите поддиректории, вы выполняете тот же поиск в них (рекурсивно). Когда вы находите файл, это аналог базового случая рекурсии.

Пример рекурсивной функции поиска файла в Python:

import os

def найти_файл(директория, имя_файла):
    for корень, директории, файлы in os.walk(директория):
        if имя_файла in файлы:
            return os.path.join(корень, имя_файла)
        for поддиректория in директории:
            результат = найти_файл(поддиректория, имя_файла)
            if результат:
                return результат
    return None

# Пример использования:
результат = найти_файл("C:/Пользователи", "документ.txt")
if результат:
    print(f"Файл найден по пути: {результат}")
else:
    print("Файл не найден.")

В этой версии функции найти_файл, если в текущей директории не найден искомый файл, она выполняет рекурсивный вызов самой себя для каждой поддиректории, продолжая поиск до тех пор, пока файл не будет найден или не будут просмотрены все директории.

Принцип работы рекурсивной функции

Принцип работы рекурсивных функций базируется на идее деления задачи на более мелкие подзадачи и решении каждой подзадачи с использованием той же функции. Это создает структуру, подобную «делению на подзадачи», которая состоит из двух основных элементов:

  1. Базовый случай (Base Case): Это условие, которое определяет точку завершения рекурсии. Когда выполнение функции достигает базового случая, она прекращает рекурсивные вызовы и возвращает результат. Базовый случай не включает рекурсивный вызов (то есть вызов функцией самое себя).
  2. Рекурсивный случай (Recursive Case): Это часть функции, где она вызывает саму себя для решения более мелкой подзадачи. Рекурсивный случай обычно включает в себя изменение параметров или данных, чтобы перейти к более простой версии задачи.

Базовых и рекурсивных случаев может быть несколько в одной функции.

Процесс работы рекурсивной функции можно представить следующим образом:

  1. Функция начинает выполнение с задачи.
  2. Она проверяет, является ли текущее состояние задачи базовым случаем. Если да, функция возвращает результат для базового случая и завершает выполнение.
  3. Если текущее состояние не является базовым, функция выполняет рекурсивный вызов самой себя, передавая измененные параметры или данные, чтобы упростить задачу.
  4. Рекурсивные вызовы продолжаются, пока не будет достигнут базовый случай, и затем результаты начинают «возвращаться» обратно по цепочке вызовов.
  5. Функция объединяет результаты всех рекурсивных вызовов, чтобы получить окончательный результат задачи.

Примеры рекурсивных задач включают вычисление факториала числа, обход деревьев или графов, сортировку и многие другие. Рекурсия предоставляет удобный способ разделения сложных задач на более простые и может сделать код более читаемым и модульным.

Пример базовых и рекурсивных случаев в рекурсивной функции

Рассмотрим задачу вычисления чисел Фибоначчи. В последовательности Фибоначчи каждое число равно сумме двух предыдущих чисел, и начинается с 0 и 1 (или 1 и 1, в зависимости от определения).

Пример рекурсивной функции для вычисления чисел Фибоначчи с несколькими базовыми случаями:

def fibonacci(n):
    if n == 0:
        return 0  # Базовый случай: F(0) = 0
    elif n == 1:
        return 1  # Базовый случай: F(1) = 1
    else:
        # Рекурсивный случай: F(n) = F(n-1) + F(n-2)
        return fibonacci(n - 1) + fibonacci(n - 2)

# Пример использования:
for i in range(10):
    print(fibonacci(i))

В этой функции есть два базовых случая: когда n равно 0 и 1. Если n равно 0, функция возвращает 0, а если n равно 1, функция возвращает 1. Эти базовые случаи обрабатываются напрямую и не вызывают рекурсивных вызовов.

В противном случае, если n больше 1, функция выполняет рекурсивные вызовы для fibonacci(n - 1) и fibonacci(n - 2), после чего суммирует результаты этих вызовов. Это позволяет вычислить число Фибоначчи для любого n, разбив задачу на две более мелкие подзадачи (расчет чисел Фибоначчи для n-1 и n-2) до достижения базовых случаев.

Как избежать бесконечной рекурсии

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

  1. Определите базовые случаи: Убедитесь, что у вас есть правильно определенные базовые случаи, которые завершают выполнение рекурсии. Базовые случаи должны быть достижимыми и приводить к возврату значения, а не вызову рекурсии.
  2. Убедитесь, что параметры сходятся к базовым случаям: Ваши рекурсивные вызовы должны уменьшаться в размере (например, уменьшение параметра n в случае вычисления чисел Фибоначчи). Это гарантирует, что рекурсия в конечном итоге достигнет базовых случаев.
  3. Проверяйте входные данные: Если ваша функция принимает входные данные, убедитесь, что они корректны. Проверяйте входные данные на предмет недопустимых значений, которые могли бы вызвать бесконечную рекурсию.
  4. Используйте отладку: Если у вас возникают сомнения относительно корректности рекурсивной функции, используйте отладочные средства, такие как вывод на экран или отладчик, чтобы отследить, какие вызовы выполняются и какие значения передаются.
  5. Ограничьте глубину рекурсии: В некоторых случаях можно использовать ограничение глубины рекурсии с помощью условий. Например, вы можете остановить рекурсию после определенного числа вызовов.

Пример:

def factorial(n):
    if n < 0:
        raise ValueError("Факториал определен только для неотрицательных чисел.")
    elif n == 0:
        return 1
    else:
        return n * factorial(n - 1)

В этой реализации факториала проверяется, что n не отрицательное число, и вызывается ошибка, если это не так. Это предотвращает бесконечную рекурсию для отрицательных значений n.

Следуя этим правилам, вы можете избежать бесконечных вызовов рекурсии и создать корректные и эффективные рекурсивные функции.

Стек вызовов функций

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

Структура данных стек можно сравнить с стопкой тарелок: вы можете добавлять или удалять тарелки только сверху стопки.

Стек работает по принципу «последним вошел — первым вышел» (Last-In-First-Out, LIFO). Это означает, что последний добавленный элемент будет первым, который можно извлечь из стека.

Стеки широко используются в программировании и компьютерных системах. Они могут использоваться для управления вызовами функций (стек вызовов функций), хранения локальных переменных функций, управления операциями отмены (откатом), хранения состояния выполнения программы и многих других задач.

Стек вызовов функции (или стек вызовов, или стек вызовов вызываемых функций) — это структура данных в компьютерной памяти, которая используется для управления вызовами функций в программе. Стек вызовов представляет собой стек данных, где каждый элемент стека представляет собой запись о вызове функции. Каждая запись содержит информацию о функции, ее параметрах и состоянии выполнения.

Когда функция вызывается, запись о вызове функции помещается в вершину стека вызовов. Когда функция завершает выполнение (возвращается), соответствующая запись удаляется из вершины стека, и выполнение продолжается с той точки, где была вызвана функция.

Принцип работы стека вызовов функции:

  1. Когда программа начинает выполнение, создается главная функция (обычно main в C/C++, __main__ в Python и т. д.), и ее запись о вызове помещается в вершину стека.
  2. При вызове другой функции, ее запись о вызове также помещается в вершину стека. Это позволяет сохранить контекст выполнения текущей функции.
  3. Когда вызываемая функция завершает выполнение (возвращает результат), ее запись удаляется из вершины стека, и выполнение продолжается с точки, где была вызвана функция.
  4. Процесс повторяется для каждого вызова функции в программе.

Стек вызовов функции важен для обеспечения корректного управления контекстом выполнения функций и возврата из функций. Это также позволяет рекурсивным функциям правильно работать, так как каждый вызов рекурсивной функции создает новую запись о вызове на вершине стека.

Если стек вызовов переполняется (например, из-за слишком глубокой рекурсии), это может привести к ошибке переполнения стека (Stack Overflow Error).

Пример стека рекурсивной функции на примере вычисления факториала

Давайте создадим простую рекурсивную функцию факториал, которая будет выводить информацию о состоянии стека вызовов при каждом вызове и завершении вызова.

a = []
def факториал(n):
    # Выводим информацию о стеке вызовов при начале выполнения функции
    s = f"Вызов: факториал({n})"
    a.append(s)
    print(f"Стек({a})")

    if n == 0 or n == 1:
        # Базовый случай: возвращаем 1
        return 1
    else:
        # Рекурсивный случай: вызываем факториал для n-1
        результат = n * факториал(n - 1)

    # Выводим информацию о стеке вызовов при завершении выполнения функции
    a.pop()
    print(f"Стек({a})")
    return результат

# Пример использования:
результат = факториал(4)
print(f"Факториал 4 равен {результат}")

Результат работы:

a = []
def факториал(n):
    # Выводим информацию о стеке вызовов при начале выполнения функции
    s = f"Вызов: факториал({n})"
    a.append(s)
    print(f"Стек({a})")

    if n == 0 or n == 1:
        # Базовый случай: возвращаем 1
        return 1
    else:
        # Рекурсивный случай: вызываем факториал для n-1
        результат = n * факториал(n - 1)

    # Выводим информацию о стеке вызовов при завершении выполнения функции
    a.pop()
    print(f"Стек({a})")
    return результат

# Пример использования:
результат = факториал(4)
print(f"Факториал 4 равен {результат}")

Результат работы:

Стек(['Вызов: факториал(4)'])
Стек(['Вызов: факториал(4)', 'Вызов: факториал(3)'])
Стек(['Вызов: факториал(4)', 'Вызов: факториал(3)', 'Вызов: факториал(2)'])
Стек(['Вызов: факториал(4)', 'Вызов: факториал(3)', 'Вызов: факториал(2)', 'Вызов: факториал(1)'])
Стек(['Вызов: факториал(4)', 'Вызов: факториал(3)', 'Вызов: факториал(2)'])
Стек(['Вызов: факториал(4)', 'Вызов: факториал(3)'])
Стек(['Вызов: факториал(4)'])
Факториал 4 равен 24

Давайте рассмотрим содержимое стека вызовов для рекурсивной функции факториал(4) (где факториал — это функция, вычисляющая факториал числа). Рекурсивно вызываемая функция факториал(n) будет вызывать саму себя, пока n не станет равным 0 или 1 (базовый случай).

стек вызовов

Итак, начнем с вызова факториал(4):

  1. факториал(4) вызывает факториал(3) и ожидает ее завершения.
  2. факториал(3) вызывает факториал(2) и ожидает ее завершения.
  3. факториал(2) вызывает факториал(1) и ожидает ее завершения.
  4. факториал(1) завершается, так как это базовый случай (факториал 1 равен 1), и возвращает результат 1.
  5. Теперь факториал(2) может продолжить выполнение, умножив результат факториал(1) (1) на 2, и возвращает 2.
  6. факториал(3) может продолжить выполнение, умножив результат факториал(2) (2) на 3, и возвращает 6.
  7. Наконец, факториал(4) может продолжить выполнение, умножив результат факториал(3) (6) на 4, и возвращает 24.

Итак, стек вызовов функций факториал содержит вызовы функций с аргументами, и каждый вызов ожидает завершения вызываемой функции до возврата результата.

Задание 36. Алгоритм вычисления значения функции F(n), где n  — целое неотрицательное число, задан следующими соотношениями:

F(n) = 0, при n ≤ 1;

F(n) = F(n − 1) + 3n2, если n > 1 и при этом нечётно;

F(n) = n / 2 + F(n − 1) + 2, если n > 1 и при этом чётно.

Чему равно значение функции F(50)? В ответе запишите только целое число.

Решение

Для вычисления значения функции F(50) по заданным соотношениям, мы можем использовать рекурсивную функцию. Однако, чтобы ускорить вычисления, мы также можем воспользоваться динамическим программированием и сохранять промежуточные результаты, чтобы избежать повторных вычислений.

def F(n, memo={}):
    if n <= 1:
        return 0
    elif n % 2 == 0:
        if n not in memo:
            memo[n] = n // 2 + F(n - 1, memo) + 2
        return memo[n]
    else:
        if n not in memo:
            memo[n] = F(n - 1, memo) + 3 * n ** 2
        return memo[n]

result = F(50)
print(result)

Вычисление F(50) с использованием этой функции даст ответ:

2864325

Задание 37. Алгоритм вычисления значения функции F(n), где n  — целое неотрицательное число, задан следующими соотношениями:

F(0) = 0;

F(n) = F(n / 2), если n > 0 и при этом чётно;

F(n) = 1 + F(n − 1), если n нечётно.

Сколько существует таких чисел n, что 1 ≤ n ≤ 1000 и F(n)  =  3

Решение

Для решения этой задачи мы можем воспользоваться рекурсивной функцией для вычисления F(n) и перебором значений n от 1 до 1000. Мы будем увеличивать счетчик, когда F(n) становится равным 3.

def F(n):
    if n == 0:
        return 0
    elif n % 2 == 0:
        return F(n // 2)
    else:
        return 1 + F(n - 1)

count = 0
for n in range(1, 1001):
    if F(n) == 3:
        count += 1

print(count)

Запустив этот код, мы получим ответ:

120

Задание 38. Напишите рекурсивную функцию new_pow(a,n) для вычисления a в степени n

Решение

Для вычисления числа a в степени n с использованием рекурсивной функции new_pow(a, n), мы можем применить следующий подход:

  1. Базовый случай: Если n равно 0, то возвращаем 1, так как любое число в степени 0 равно 1.
  2. Рекурсивный случай: В противном случае, вычисляем a в степени n-1 с помощью рекурсии и умножаем на a.
def new_pow(a, n):
    # Базовый случай: a^0 = 1
    if n == 0:
        return 1
    # Рекурсивный случай: a^n = a^(n-1) * a
    else:
        return a * new_pow(a, n - 1)

# Пример использования:
result = new_pow(2, 3)  # Вычисляем 2 в степени 3
print(result)  # Вывод: 8

Задание 39.

Найти n-ное число Фибоначчи, где n — это неотрицательное целое число, а ряд Фибоначчи определен следующим образом:

  1. F(0) = 0
  2. F(1) = 1
  3. F(n) = F(n-1) + F(n-2), для n > 1

Требуется написать рекурсивную функцию, которая вычисляет n-ное число Фибоначчи согласно указанным условиям.

Решение

Для решения этой задачи с использованием рекурсии, мы можем определить рекурсивную функцию fibonacci(n), которая будет вычислять n-ное число Фибоначчи. В этой функции мы учтем базовые случаи (F(0) и F(1)) и затем будем использовать рекурсию для вычисления остальных значений. Важно помнить, что рекурсивное решение может быть неэффективным для больших n, так как оно может вызвать множество повторных вычислений.

def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

Задание 40. Положительные числа вводятся с клавиатуры. Окончание ввода — число 0. Напишите рекурсивную функцию для вывода этих чисел в обратном порядке. Нельзя использовать списки и другие структуры данных для хранения промежуточных значений.

Только рекурсивные вызовы, input() и print()

Решение
def ввод_и_вывод_в_обратном_порядке():
    число = int(input("Введите положительное число (для окончания введите 0): "))
    
    if число != 0:
        # Рекурсивный вызов функции
        ввод_и_вывод_в_обратном_порядке()
        
        # Выводим число после завершения рекурсии (при возврате из рекурсии)
        print(число)

# Начинаем ввод чисел и выводим их в обратном порядке
print("Числа в обратном порядке:")
ввод_и_вывод_в_обратном_порядке()

Этот код позволяет вводить положительные числа с клавиатуры и выводит их в обратном порядке без использования списка или других структур данных.

Рекурсивная функция ввод_и_вывод_в_обратном_порядке() вызывается до ввода числа 0, и после завершения каждого вызова выводится число. Таким образом, числа выводятся в обратном порядке, и структура данных для хранения чисел не требуется.

Задание 41. Дано натуральное число n. Выведите все числа от 1 до n, используя рекурсивную функцию. Циклы использовать запрещено.

Решение

Для вывода всех чисел от 1 до n с использованием рекурсивной функции, мы можем следовать следующему подходу:

  1. В базовом случае, когда n равно 1, просто выводим число 1 и завершаем рекурсию.
  2. В промежуточном случае, вызываем рекурсивную функцию для вывода чисел от 1 до n-1.
  3. После завершения рекурсии, выводим число n.
def вывод_чисел_от_1_до_n(n):
    if n == 1:
        print(n)  # Базовый случай: выводим число 1 и завершаем рекурсию
    else:
        вывод_чисел_от_1_до_n(n - 1)  # Рекурсивный вызов для вывода чисел от 1 до n-1
        print(n)  # Выводим число n после завершения рекурсии

# Пример использования:
n = int(input("Введите натуральное число n: "))
вывод_чисел_от_1_до_n(n)

Задание 42. Даны два целых числа A и В. Выведите все числа от A до B включительно, в порядке возрастания, если A < B, или в порядке убывания в противном случае. Используя рекурсивную функцию. Циклы использовать запрещено.

Решение

Для вывода всех чисел от A до B включительно в порядке возрастания, если A < B, или в порядке убывания, если A >= B, с использованием рекурсивной функции, мы можем использовать следующий подход:

  1. В базовом случае, когда A и B равны (т.е., A == B), просто выводим число A (или B) и завершаем рекурсию.
  2. В промежуточном случае, если A < B, вызываем рекурсивную функцию для вывода чисел от A до B-1. После завершения рекурсии выводим число B.
  3. В промежуточном случае, если A >= B, вызываем рекурсивную функцию для вывода чисел от A до B+1. После завершения рекурсии выводим число B.
def вывод_чисел_от_A_до_B(A, B):
    if A == B:
        print(A)  # Базовый случай: выводим число A (или B) и завершаем рекурсию
    elif A < B:
        вывод_чисел_от_A_до_B(A, B - 1)  # Рекурсивный вызов для вывода чисел от A до B-1
        print(B)  # Выводим число B после завершения рекурсии
    else:
        вывод_чисел_от_A_до_B(A, B + 1)  # Рекурсивный вызов для вывода чисел от A до B+1
        print(B)  # Выводим число B после завершения рекурсии

# Пример использования:
A = int(input("Введите целое число A: "))
B = int(input("Введите целое число B: "))
вывод_чисел_от_A_до_B(A, B)

Задание 43. Напишите рекурсивную функцию перевода числа из десятичной в двоичную систему счисления.

Решение

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

  1. Базовый случай: Если число n равно 0, возвращаем строку «0», так как 0 в двоичной системе также равно 0.
  2. Рекурсивный случай: В противном случае, разделяем число n на два целых числа: целую часть n // 2 и остаток n % 2. Затем вызываем рекурсивную функцию для целой части и объединяем результат с остатком.
def десятичное_в_двоичное(n):
    if n == 0:
        return "0"  # Базовый случай: 0 в двоичной системе равно 0
    else:
        # Рекурсивный случай: переводим целую часть и добавляем остаток
        return десятичное_в_двоичное(n // 2) + str(n % 2)

# Пример использования:
число = int(input("Введите десятичное число: "))
бинарное_число = десятичное_в_двоичное(число)
print(f"Число {число} в двоичной системе: {бинарное_число}")

Задание 44. Даны два числа m и n. Напишите рекурсивную функцию, находящую наибольший общий делитель двух чисел.

Решение

Для нахождения наибольшего общего делителя (НОД) двух чисел m и n с использованием рекурсивной функции, мы можем воспользоваться алгоритмом Евклида. Алгоритм Евклида заключается в следующем:

  1. Базовый случай: Если одно из чисел равно 0, то НОД равен другому числу. То есть, если n равно 0, то НОД равен m, и наоборот.
  2. Рекурсивный случай: В противном случае, мы вычисляем НОД чисел n и m % n. То есть, НОД числа m и n равен НОДу числа n и остатка от деления m на n.
def наибольший_общий_делитель(m, n):
    if n == 0:
        return m  # Базовый случай: НОД числа m и 0 равен m
    else:
        return наибольший_общий_делитель(n, m % n)  # Рекурсивный случай

# Пример использования:
m = int(input("Введите первое число: "))
n = int(input("Введите второе число: "))

результат = наибольший_общий_делитель(m, n)
print(f"Наибольший общий делитель чисел {m} и {n}: {результат}")

Задание 45. С помощью рекурсивных функций, найдите сумму, произведение и максимальное значение элементов числовой последовательности. Стандартные агрегатные функции (sum, max) использовать запрещается.

Решение

Для нахождения суммы, произведения и максимального значения элементов числовой последовательности с использованием рекурсивных функций, мы можем написать три отдельные рекурсивные функции для каждой из этих задач. Вот как это можно сделать:

  1. Для нахождения суммы элементов последовательности:
    • Базовый случай: Если последовательность пуста (нет элементов), вернуть 0.
    • Рекурсивный случай: Извлечь первый элемент, вызвать функцию рекурсивно для оставшихся элементов и сложить первый элемент с результатом.
  2. Для нахождения произведения элементов последовательности:
    • Базовый случай: Если последовательность пуста (нет элементов), вернуть 1 (так как умножение на 1 не меняет результат).
    • Рекурсивный случай: Извлечь первый элемент, вызвать функцию рекурсивно для оставшихся элементов и умножить первый элемент на результат.
  3. Для нахождения максимального значения элементов последовательности:
    • Базовый случай: Если последовательность пуста (нет элементов), вернуть None (так как нет максимального значения).
    • Рекурсивный случай: Извлечь первый элемент, вызвать функцию рекурсивно для оставшихся элементов и сравнить первый элемент с результатом, возвращая большее из двух.
def сумма_последовательности(последовательность):
    if not последовательность:
        return 0  # Базовый случай: последовательность пуста
    else:
        первый_элемент = последовательность[0]
        остаток_последовательности = последовательность[1:]
        return первый_элемент + сумма_последовательности(остаток_последовательности)

def произведение_последовательности(последовательность):
    if not последовательность:
        return 1  # Базовый случай: последовательность пуста
    else:
        первый_элемент = последовательность[0]
        остаток_последовательности = последовательность[1:]
        return первый_элемент * произведение_последовательности(остаток_последовательности)

def максимальное_значение_последовательности(последовательность):
    if not последовательность:
        return None  # Базовый случай: последовательность пуста
    else:
        первый_элемент = последовательность[0]
        остаток_последовательности = последовательность[1:]
        максимум_остатка = максимальное_значение_последовательности(остаток_последовательности)
        if максимум_остатка is None or первый_элемент > максимум_остатка:
            return первый_элемент
        else:
            return максимум_остатка

# Пример использования:
последовательность = [2, 4, 6, 8, 10]
сумма = сумма_последовательности(последовательность)
произведение = произведение_последовательности(последовательность)
максимум = максимальное_значение_последовательности(последовательность)

print(f"Сумма: {сумма}")
print(f"Произведение: {произведение}")
print(f"Максимум: {максимум}")

Вложенные функции

Вложенные функции (также известные как внутренние функции или nested functions) — это концепция в программировании, при которой одна функция определяется и вызывается внутри другой функции. То есть, вложенная функция существует и работает в контексте внешней функции, и она имеет доступ к локальным переменным и параметрам внешней функции. Это может быть полезно для организации и улучшения структуры кода и скрытия деталей реализации.

Вот некоторые ключевые аспекты вложенных функций:

  1. Область видимости: Вложенные функции имеют доступ к переменным, определенным внутри внешней функции, а также к глобальным переменным. Однако внешняя функция не имеет доступа к переменным, определенным внутри вложенной функции.
  2. Замыкание (closures): Вложенные функции могут «захватывать» (capturing) переменные из внешней функции. Это означает, что вложенная функция сохраняет доступ к переменным даже после завершения выполнения внешней функции. Это создает замыкание — функцию, которая сохраняет состояние окружающего контекста.
  3. Локальные функции: Вложенные функции могут использоваться для создания локальных (приватных) функций, которые не видны извне внешней функции. Это может помочь в скрытии деталей реализации и предоставлении более чистого интерфейса.

Пример реализации

def внешняя_функция(x):
    def вложенная_функция(y):
        return x + y
    return вложенная_функция

результат = внешняя_функция(10)
print(результат(5))  # Результат: 15

В этом примере внешняя_функция возвращает вложенную функцию . То еть, функция, может возвращать не только число, строку, список, класс, но и другую функцию! И еще один вывод из этого кода. Функцию можно поместить в переменную, которую затем вызвать с аргументом.

вложенная_функция, которая имеет доступ к аргументу x из внешней функции. Вызов результат(5) вызывает вложенную функцию с аргументом 5, и она возвращает сумму 10 + 5.

Использование вложенных функций может сделать код более читаемым, организованным и изолированным, что способствует поддержке и уменьшает конфликты имен в больших программах.

Организация замыкания в Python

Замыкание (closure) — это функция, которая сохраняет доступ к переменным из окружающей её области видимости, даже после завершения выполнения этой области видимости.

Это часто используется для создания функций, которые могут захватывать и изменять переменные внешней функции, создавая таким образом гибкую и переиспользуемую логику. Вот пример организации замыкания на Python:

def умножитель(x):
    # Внутренняя функция, которая является замыканием
    def внутренняя(y):
        return x * y
    return внутренняя

# Создаем замыкание с аргументом x = 5
умножить_на_5 = умножитель(5)

# Теперь мы можем вызывать замыкание с разными значениями y
результат1 = умножить_на_5(2)  # Результат: 10 (5 * 2)
результат2 = умножить_на_5(3)  # Результат: 15 (5 * 3)

print(результат1)
print(результат2)

Локальные функции

Локальные функции — это функции, объявленные внутри другой функции, и они видны и доступны только внутри этой внешней функции.

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

def внешняя_функция():
    # Это локальная функция
    def локальная_функция():
        print("Это локальная функция")

    # Вызов локальной функции
    локальная_функция()

# Попытка вызова локальной функции за пределами внешней функции приведет к ошибке
внешняя_функция()
# локальная_функция()  # Этот вызов вызовет ошибку NameError

В этом примере локальная_функция объявлена внутри функции внешняя_функция. Она видна и может быть вызвана только внутри внешняя_функция. Если мы попытаемся вызвать локальная_функция() за пределами внешняя_функция, это вызовет ошибку NameError, так как локальная_функция не видна в глобальной области видимости.

Локальные функции часто используются для выделения части функциональности внутри функции, делая код более читаемым и поддерживаемым, а также для скрытия деталей реализации, которые не должны быть доступны извне функции.

Задание 46. Напишите функцию среднее_с_фильтром, которая принимает список чисел и фильтр (функцию-предикат), и возвращает среднее значение только для чисел, удовлетворяющих условию фильтра.

Решение
def среднее_с_фильтром(список, фильтр):
    числа = [x for x in список if фильтр(x)]
    
    def среднее():
        return sum(числа) / len(числа) if числа else None
    
    return среднее

фильтр_положительных = lambda x: x > 0
фильтр_четных = lambda x: x % 2 == 0

среднее_положительных = среднее_с_фильтром([-1, 2, 4, -3, 5], фильтр_положительных)
среднее_четных = среднее_с_фильтром([1, 2, 3, 4, 5], фильтр_четных)

print(среднее_положительных())  # Результат: 3.6666666666666665
print(среднее_четных())  # Результат: 3.0

Задание 47. Создайте функцию генератор_имен, которая генерирует уникальные имена на основе заданного префикса и суффикса. Внутри функции используйте вложенную функцию для отслеживания уже сгенерированных имен.

Решение
def генератор_имен(префикс, суффикс):
    уже_сгенерированные = set()
    
    def новое_имя():
        имя = f"{префикс}_{len(уже_сгенерированные)}_{суффикс}"
        уже_сгенерированные.add(имя)
        return имя
    
    return новое_имя

создать_имя = генератор_имен("пользователь", "email")
print(создать_имя())  # Результат: "пользователь_0_email"
print(создать_имя())  # Результат: "пользователь_1_email"

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

Решение
def кэширование(функция):
    кэш = {}
    
    def внутренняя(*аргументы):
        if аргументы not in кэш:
            результат = функция(*аргументы)
            кэш[аргументы] = результат
        return кэш[аргументы]
    
    return внутренняя

# Пример функции, которую мы хотим кэшировать
def сложение(a, b):
    print("Выполнено сложение")
    return a + b

кэшированное_сложение = кэширование(сложение)

print(кэшированное_сложение(2, 3))  # Результат: "Выполнено сложение" и 5
print(кэшированное_сложение(2, 3))  # Результат: 5 (значение взято из кэша, функция не вызывается повторно)

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

nonlocal переменные

Ключевое слово nonlocal в Python используется внутри функций для указания, что переменная не является локальной, но также не является глобальной. Она находится в объемлющей (внешней) области видимости, но не в глобальной.

Посмотрим на пример, чтобы лучше понять, как работает nonlocal

def внешняя_функция():
    переменная = 10

    def внутренняя_функция():
        nonlocal переменная
        переменная += 1
        print("Внутренняя функция:", переменная)

    внутренняя_функция()
    print("Внешняя функция:", переменная)

внешняя_функция()

В этом примере переменная — это переменная внутри внешняя_функция(). Когда мы вызываем внутренняя_функция(), и используем nonlocal переменная, мы указываем, что мы хотим изменить значение переменной, которая находится в объемлющей области видимости (в данном случае, в внешняя_функция()), а не создавать новую локальную переменную.

Вывод будет:

Внутренняя функция: 11
Внешняя функция: 11

Без использования nonlocal, если бы мы попытались изменить значение переменной внутри внутренняя_функция(), была бы создана новая локальная переменная, и изменения не затронули бы переменную из внешняя_функция().

Декораторы

Декораторы — это специальные функции в Python, которые используются для изменения поведения других функций или методов без изменения их собственного кода.

Декораторы представляют собой мощный инструмент, который позволяет добавлять функциональность к существующим функциям или классам.

Давайте рассмотрим это на примере:

Представьте, что у вас есть функция, которая выполняет какое-то действие:

def привет():
    return "Привет, мир!"

Теперь предположим, что вы хотите добавить к этой функции дополнительный функционал, чтобы перед ее выполнением выводилось текущее время. Вместо того, чтобы изменять код функции привет(), вы можете использовать декоратор. Вот как это делается:

import datetime

# Это декоратор
def добавить_время(func):
    def обернутая_функция():
        текущее_время = datetime.datetime.now()
        print(f"Текущее время: {текущее_время}")
        return func()
    return обернутая_функция

@добавить_время
def привет():
    return "Привет, мир!"

# Теперь вызываем функцию привет() с декоратором
привет()

В данном примере добавить_время — это декоратор. Он принимает функцию func, оборачивает ее внутри новой функции обернутая_функция, которая добавляет вывод текущего времени перед выполнением func(). Затем мы используем декоратор @добавить_время перед определением функции привет(), чтобы указать, что мы хотим применить этот декоратор к функции привет().

Таким образом, при вызове привет(), декоратор добавить_время добавляет вывод времени перед выполнением функции привет(). Это позволяет изменять поведение функций, не изменяя их код напрямую.

Синтаксис декоратора

Синтаксис декораторов в Python достаточно прост и понятен. Декораторы используют символ «@» перед определением функции или метода, которому нужно применить декоратор. Вот общий синтаксис:

@декоратор
def функция():
    # Тело функции
  • @декоратор — это символ @, за которым следует имя функции-декоратора (без вызова). Это указывает на то, что функция декоратор должна быть применена к функции, определенной ниже.

Давайте рассмотрим пример с декоратором:

def декоратор(func):
    def обертка():
        print("До выполнения функции")
        func()
        print("После выполнения функции")
    return обертка

@декоратор
def привет():
    print("Привет, мир!")

привет()

В этом примере декоратор — это функция, которая принимает другую функцию func и возвращает новую функцию обертка. Затем мы используем декоратор @декоратор перед определением функции привет(). Это приводит к тому, что функция привет() оборачивается вокруг функции обертка, и при вызове привет() сначала выполняется код из функции обертка, а затем код из функции привет().

Практическое применение декораторов

Логирование

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

import logging

def логировать(func):
    def обертка(*args, **kwargs):
        logging.info(f"Вызов функции {func.__name__} с аргументами {args} и {kwargs}")
        результат = func(*args, **kwargs)
        logging.info(f"Функция вернула результат: {результат}")
        return результат
    return обертка

@логировать
def добавить(a, b):
    return a + b

результат = добавить(3, 5)

В этом примере декоратор логировать записывает информацию о вызове функции добавить и ее результате в журнал.

Кэширование

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

кэш = {}

def кэшировать(func):
    def обертка(n):
        if n in кэш:
            return кэш[n]
        результат = func(n)
        кэш[n] = результат
        return результат
    return обертка

@кэшировать
def факториал(n):
    if n == 0:
        return 1
    else:
        return n * факториал(n - 1)

результат1 = факториал(5)  # Вычисляется и кэшируется
результат2 = факториал(5)  # Получается из кэша

В этом примере декоратор кэшировать сохраняет результаты вычисления факториала для избегания повторных вычислений при одних и тех же аргументах.

Декораторы с параметрами

Декораторы с параметрами в Python реализуются с использованием двух уровней функций. Внешняя функция принимает параметры и возвращает внутреннюю функцию, которая, в свою очередь, принимает функцию для декорирования и возвращает новую функцию.

def декоратор_с_параметром(параметр):
    def внутренний_декоратор(func):
        def обертка(*args, **kwargs):
            print(f"Декорировано с параметром: {параметр}")
            результат = func(*args, **kwargs)
            return результат
        return обертка
    return внутренний_декоратор

# Использование декоратора с параметром
@декоратор_с_параметром("Параметр 1")
def пример_функции():
    print("Пример функции")

пример_функции()

В этом примере декоратор_с_параметром — это внешняя функция, которая принимает параметр и возвращает внутреннюю функцию внутренний_декоратор. Эта внутренняя функция, в свою очередь, принимает функцию func и возвращает новую функцию обертка. При использовании декоратора с параметром, вы вызываете внешнюю функцию с параметром, а затем полученную функцию вызываете с целевой функцией.

Если вам нужно передавать различные параметры для разных функций, вы можете создать декоратор с параметром, вызывать его с нужными значениями и затем использовать его для декорирования функций.

@декоратор_с_параметром("Параметр 1")
def функция_1():
    print("Функция 1")

@декоратор_с_параметром("Параметр 2")
def функция_2():
    print("Функция 2")

функция_1()
функция_2()

Пример реализации декоратора с параметром.

Допустим, у нас есть задача измерения времени выполнения функций, и мы хотим создать декоратор, который позволяет нам указывать, нужно ли выводить время выполнения или нет.

import time

def декоратор_измерения_времени(выводить_время):
    def внутренний_декоратор(func):
        def обертка(*args, **kwargs):
            начало = time.time()
            результат = func(*args, **kwargs)
            конец = time.time()
            
            if выводить_время:
                print(f"Время выполнения {func.__name__}: {конец - начало} секунд")

            return результат
        return обертка
    return внутренний_декоратор

# Используем декоратор с параметром
@декоратор_измерения_времени(выводить_время=True)
def функция_с_задержкой():
    time.sleep(2)
    print("Функция завершена")

функция_с_задержкой()

В этом примере декоратор_измерения_времени — это внешняя функция, принимающая параметр выводить_время. Внутри она возвращает внутреннюю функцию внутренний_декоратор, которая принимает целевую функцию func и возвращает новую функцию обертка. В зависимости от значения параметра выводить_время декоратор будет выводить или не выводить информацию о времени выполнения функции.

Таким образом, при использовании декоратора с параметром @декоратор_измерения_времени(выводить_время=True) мы указываем, что хотим измерять и выводить время выполнения функции.

Задание 49.

Мониторинг времени выполнения функции

Создайте декоратор, который будет измерять и выводить время выполнения функции. Декоратор должен принимать на вход имя функции и передавать ему аргументы. Замерьте время выполнения функции и выведите его на экран. Продемонстрируйте работу декоратора на примере функции, которая вычисляет факториал числа.

Решение
import time

def измерить_время(func):
    def обертка(*args, **kwargs):
        начало = time.time()
        результат = func(*args, **kwargs)
        конец = time.time()
        print(f"Время выполнения функции {func.__name__}: {конец - начало} секунд")
        return результат
    return обертка

@измерить_время
def вычислить_факториал(n):
    if n == 0:
        return 1
    else:
        return n * вычислить_факториал(n - 1)

результат = вычислить_факториал(5)

Задание 50.

Контроль доступа к функциям

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

Решение
def контроль_доступа(минимальный_уровень):
    def декоратор(func):
        def обертка(*args, **kwargs):
            текущий_уровень = получить_уровень_доступа()
            if текущий_уровень >= минимальный_уровень:
                return func(*args, **kwargs)
            else:
                print("Отказано в доступе")
        return обертка
    return декоратор

@контроль_доступа(2)  # Функцию с уровнем доступа 2 могут выполнять пользователи с уровнем 2 и выше
def выполнить_важную_операцию():
    print("Важная операция выполнена")

выполнить_важную_операцию()

Задание 51.

Кэширование результатов функции

Создайте декоратор, который будет кэшировать результаты выполнения функции для ускорения работы программы. Декоратор должен использовать словарь для хранения результатов и проверять, есть ли уже результат для заданных аргументов. Если результат есть, он возвращается из кэша; в противном случае функция выполняется и результат сохраняется в кэше.

Решение
кэш = {}

def кэшировать(func):
    def обертка(*args, **kwargs):
        ключ = (args, frozenset(kwargs.items()))
        if ключ in кэш:
            return кэш[ключ]
        результат = func(*args, **kwargs)
        кэш[ключ] = результат
        return результат
    return обертка

@кэшировать
def сложить(a, b):
    return a + b

результат1 = сложить(3, 5)  # Вычисляется и кэшируется
результат2 = сложить(3, 5)  # Получается из кэша

Модуль functools

functools — это модуль в стандартной библиотеке Python, предоставляющий некоторые полезные функции высшего порядка (higher-order functions), а также инструменты для работы с функциями. Рассмотрим некоторые из возможностей модуля functools.

  1. functools.partial: Эта функция позволяет создавать новую функцию, фиксируя некоторые из ее аргументов. Таким образом, вы можете создавать новые функции на основе существующих, фиксируя часть аргументов.
    from functools import partial
    
    # Пример использования partial
    def умножить(a, b):
        return a * b
    
    умножить_на_2 = partial(умножить, 2)
    результат = умножить_на_2(5)  # результат = 2 * 5
    
  2. functools.wraps: Декоратор wraps позволяет сохранять метаинформацию (например, докстринги, имена) при создании декорированных функций.
    from functools import wraps
    
    def декоратор(func):
        @wraps(func)
        def обертка(*args, **kwargs):
            """Докстринг обертки."""
            print(f"Вызов функции {func.__name__}")
            return func(*args, **kwargs)
        return обертка
    
    @декоратор
    def пример_функции():
        """Докстринг примера функции."""
        print("Пример функции")
    
    print(пример_функции.__name__)  # Выведет "пример_функции"
    print(пример_функции.__doc__)   # Выведет "Докстринг примера функции."
    
  3. functools.lru_cache: Декоратор lru_cache (Least Recently Used Cache) предоставляет мемоизацию (кеширование) для функций с автоматическим удалением наименее недавно используемых результатов.
    from functools import lru_cache
    
    @lru_cache(maxsize=None)
    def факториал(n):
        if n == 0:
            return 1
        else:
            return n * факториал(n-1)
    
  4. functools.reduce: Функция reduce применяет бинарную функцию к элементам последовательности, сводя ее к единственному значению. Обычно используется совместно с библиотечной функцией operator.
    from functools import reduce
    from operator import add
    
    список = [1, 2, 3, 4, 5]
    сумма = reduce(add, список)
    

Практическое применение functools

Кэширование для ускорения рекурсивных вычислений

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    else:
        return fib(n-1) + fib(n-2)

# Пример использования
результат = fib(10)  # Вычисление числа Фибоначчи с использованием мемоизации
print(результат)

Обертывание функции с фиксированными аргументами

from functools import partial

def приветствие(приветствие, имя):
    print(f"{приветствие}, {имя}!")

# Создание обернутой функции с фиксированным аргументом
здравствуйте = partial(приветствие, "Здравствуйте")

# Пример использования
здравствуйте("Анна")  # Вывод: Здравствуйте, Анна!
здравствуйте("Иван")  # Вывод: Здравствуйте, Иван!

Декоратор для проверки типов аргументов

from functools import wraps

def проверка_типов(func):
    @wraps(func)
    def обертка(*args, **kwargs):
        for аргумент, тип_аргумента in zip(args, func.__annotations__.values()):
            if not isinstance(аргумент, тип_аргумента):
                raise TypeError(f"Тип аргумента должен быть {тип_аргумента}")
        return func(*args, **kwargs)
    return обертка

# Использование декоратора
@проверка_типов
def сложение(a: int, b: int) -> int:
    return a + b

# Пример использования
результат = сложение(2, 3)  # Вернет 5

 

Понравилась статья? Поделиться с друзьями:
Школа Виктора Комлева
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.