Python: Замыкания и декораторы для начинающих

Замыкания

Замыкания (в некоторых источниках – фабричные функции) – это функции, определяемые и возвращаемые другой функцией. Замыкание получает доступ к значениям и объектам из области видимости родительской (внешней) функции, независимо от того, из какой области видимости происходит вызов замыкания. Главная особенность: замыкание имеет полный доступ к переменным и именам, определённым в локальном пространстве имён, в котором оно было создано, даже если внешняя функция завершила своё выполнение. Мы, по сути, сохраняем некое состояние, даже после завершения внешней функции.

Чтобы определить замыкание, нужно выполнить три шага:

  1. Создать вложенную функцию.
  2. В этой функции сослаться на переменные из внешней функции.
  3. Вернуть вложенную функцию.

Пример:

def внешняя_функция():
    a = 10
    b = 5
    
    def вложенная_функция():
        return a + b
    
    return вложенная_функция

cl = внешняя_функция()
сумма = cl()
print(сумма)  # Вывод: 15

В этом примере вложенная_функция (замыкание) имеет доступ к переменным a и b из внешняя_функция, даже после завершения её выполнения. Функция возвращает ссылку на объект (вложенную функцию), а не копию данных.

Можно немного изменить запись:

print(внешняя_функция()())  # Вывод: 15

Здесь мы вызываем внешнюю функцию, а её результат (вложенную функцию) сразу вызываем.

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

Пример с изменением объекта:

def внешняя_функция(список):
    def вложенная_функция():
        return список[0] + список[1]
    return вложенная_функция

сп = [1, 2, 3, 4]
cl = внешняя_функция(сп)
print(cl())  # Вывод: 3

сп[0] = 100
print(cl())  # Вывод: 102

Здесь изменение элемента списка сп отражается на результате работы замыкания. Мы работаем с самим объектом, а не его копией.

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

Декораторы

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

Создание декоратора:

def мой_декоратор(функция):
    def обёртка():
        print("до")
        функция()
        print("после")
    return обёртка

@мой_декоратор
def моя_функция():
    print("основная функция!!!")

моя_функция()

Здесь мой_декоратор – декоратор, обёртка – вложенная функция. Первый способ декорирования – присваивание результата декоратора переменной, а второй – использование символа @ перед функцией.

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

def мой_декоратор(функция):
    def обёртка(a):
        print("до")
        результат = функция(a)
        print("после")
        return результат
    return обёртка

@мой_декоратор
def моя_функция(a):
    return a**a

print(моя_функция(8)) # вывод 8^8

Здесь мы добавили параметр a в декоратор и декорируемую функцию.

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

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

def мой_декоратор(функция):
    def обёртка():
        текст = функция()
        return текст.upper() + "!"
    return обёртка

@мой_декоратор
def моя_функция():
    return "информатика"

print(моя_функция())  # Вывод: ИНФОРМАТИКА!

Наложение декораторов:

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

def декор1(функция):
    # ...
    return обёртка

def декор2(функция):
    # ...
    return обёртка

@декор1
@декор2
def моя_функция():
    # ...

Декораторы применяются снизу вверх.

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

Что будем искать? Например,программа