Замыкания
Замыкания (в некоторых источниках – фабричные функции) – это функции, определяемые и возвращаемые другой функцией. Замыкание получает доступ к значениям и объектам из области видимости родительской (внешней) функции, независимо от того, из какой области видимости происходит вызов замыкания. Главная особенность: замыкание имеет полный доступ к переменным и именам, определённым в локальном пространстве имён, в котором оно было создано, даже если внешняя функция завершила своё выполнение. Мы, по сути, сохраняем некое состояние, даже после завершения внешней функции.
Чтобы определить замыкание, нужно выполнить три шага:
- Создать вложенную функцию.
- В этой функции сослаться на переменные из внешней функции.
- Вернуть вложенную функцию.
Пример:
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. Наиболее распространён способ декорирования с использованием символа @. Понимание декораторов упростит работу с более сложным кодом.