Замыкания в Python
Замыкание в Python — это вложенная функция, которая позволяет обращаться к переменным внешней функции даже после её завершения.
Прежде чем изучать замыкания, повторим концепцию вложенных функций в Python.
Вложенные функции в Python
В Python мы можем создать функцию внутри другой функции. Это называется вложенной функцией. Например,
def greet(name):
# внутренняя функция
def display_name():
print("Hi", name)
# вызов внутренней функции
display_name()
# вызов внешней функции
greet("John")
# Вывод: Hi John
В приведённом примере мы определили функцию display_name() внутри функции greet().
Здесь display_name() — вложенная функция. Вложенная функция работает аналогично обычной функции. Она выполняется, когда display_name() вызывается внутри функции greet().
Замыкания в Python
Как мы уже обсуждали, замыкание — это вложенная функция, которая помогает обращаться к переменным внешней функции даже после её завершения. Например,
def greet():
# переменная, определённая вне внутренней функции
name = "John"
# возврат вложенной анонимной функции
return lambda: "Hi " + name
# вызов внешней функции
message = greet()
# вызов внутренней функции
print(message())
# Вывод: Hi John
В приведённом примере мы создали функцию greet(), возвращающую вложенную анонимную функцию.
Здесь, когда мы вызываем внешнюю функцию:
message = greet()
Возвращённая функция теперь присваивается переменной message.
В этот момент выполнение внешней функции завершено, поэтому переменная name должна быть уничтожена. Однако когда мы вызываем анонимную функцию:
print(message())
мы можем обратиться к переменной name внешней функции.
Это возможно, потому что вложенная функция теперь действует как замыкание, которое сохраняет переменную внешней области видимости в своей области даже после выполнения внешней функции.
Рассмотрим ещё один пример, чтобы прояснить эту концепцию.
Пример: вывод нечётных чисел с помощью замыкания
def calculate():
num = 1
def inner_func():
nonlocal num
num += 2
return num
return inner_func
# вызов внешней функции
odd = calculate()
# вызов внутренней функции
print(odd())
print(odd())
print(odd())
# повторный вызов внешней функции
odd2 = calculate()
print(odd2())
Вывод
3
5
7
3
В приведённом примере:
odd = calculate()
Этот код выполняет внешнюю функцию calculate() и возвращает замыкание для нечётного числа.
Поэтому мы можем обращаться к переменной num функции calculate() даже после завершения внешней функции.
Снова, когда мы вызываем внешнюю функцию через:
odd2 = calculate()
возвращается новое замыкание. Поэтому при вызове odd2() мы снова получаем 3.
Когда использовать замыкания?
Для чего же нужны замыкания?
Замыкания могут использоваться для избегания глобальных значений и обеспечения сокрытия данных и могут быть элегантным решением для простых случаев с одним или несколькими методами.
Однако для более крупных случаев с несколькими атрибутами и методами реализация через класс может быть более подходящей.
def make_multiplier_of(n):
def multiplier(x):
return x * n
return multiplier
# умножитель на 3
times3 = make_multiplier_of(3)
# умножитель на 5
times5 = make_multiplier_of(5)
# Вывод: 27
print(times3(9))
# Вывод: 15
print(times5(3))
# Вывод: 30
print(times5(times3(2)))
Декораторы Python также широко используют замыкания.
В заключение стоит отметить, что значения, заключённые в функцию-замыкание, можно узнать.
У всех функциональных объектов есть атрибут __closure__, который возвращает кортеж объектов-ячеек, если это функция-замыкание.
Возвращаясь к примеру выше, мы знаем, что times3 и times5 — функции-замыкания.