Замыкание — это функция, которая определена внутри другой функции и использует переменные из локальной области видимости внешней функции. Эти переменные остаются доступными для вложенной функции даже после завершения выполнения внешней функции, что позволяет ей использовать их в дальнейшем.
Замыкания часто используются в функциональных языках программирования. Python их поддерживает, замыкания в нём могут быть очень полезными, так как они, например, позволяют создавать декораторы на основе функций.
В этой статье мы:
- Разберем, что такое замыкания и как они работают в Python.
- Узнаем, как и где их можно применять.
- Изучим альтернативы замыканиям.
Чтобы всё понять в этой статье, вам желательно знать, что такое функции, вложенные функции, декораторы и классы в Python.
def outer_func():
name = "username"
def inner_func():
print(f"Hello, {name}!")
inner_func()
outer_func() # Hello, username!
greeter = outer_func()
print(greeter) # None
def outer_func():
name = "username"
def inner_func():
print(f"Hello, {name}!")
return inner_func
outer_func() # <function outer_func.<locals>.inner_func at ...>
greeter = outer_func()
greeter() # Hello, username!
Для создания замыкания в Python нам понадобятся три компонента:
- Внешняя функция: Это функция, которая содержит другую функцию, называемую внутренней. Внешняя функция может принимать аргументы и определять переменные, к которым внутренняя функция может получить доступ и обновить..
- Локальные переменные внешней функции: Это переменные, определенные внутри внешней функции. Python сохраняет эти переменные, позволяя использовать их в замыкании, даже после того, как внешняя функция завершила свою работу.
- Вложенная функция: Это функция, определенная внутри внешней функции. Она может получать доступ и обновлять переменные из внешней функции, даже после того, как внешняя функция вернула значение.
def outer_func():
name = "username"
return lambda: print(f"Hello, {name}!")
greeter = outer_func()
greeter() # Hello, username!
def outer_func(outer_arg):
local_var = "Внешняя локальная переменная"
def closure():
print(outer_arg)
print(local_var)
print(another_local_var)
another_local_var = "Другая внешняя локальная переменная"
return closure
closure = outer_func("Внешний аргумент")
closure()
# Внешний аргумент
# Внешняя локальная переменная
# Другая внешняя локальная переменная
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter = make_counter()
counter() # 1
counter() # 2
counter() # 3
def make_appender():
items = []
def appender(new_item):
items.append(new_item)
return items
return appender
appender = make_appender()
appender("Первый элемент")
# ['Первый элемент']
appender("Второй элемент")
# ['Первый элемент', 'Второй элемент']
appender("Третий элемент")
# ['Первый элемен', 'Второй элемент', 'Третий элемент']
def make_root_calculator(root_degree, precision=2):
def root_calculator(number):
return round(pow(number, 1 / root_degree), precision)
return root_calculator
square_root = make_root_calculator(2, 4)
square_root(42) # 6.4807
cubic_root = make_root_calculator(3)
cubic_root(42) # 3.48
def cumulative_average():
data = []
def average(value):
data.append(value)
return sum(data) / len(data)
return average
stream_average = cumulative_average()
stream_average(12) # 12.0
stream_average(13) # 12.5
stream_average(11) # 12.0
stream_average(10) # 11.5
import tkinter as tk
app = tk.Tk()
app.title("GUI App")
app.geometry("320x240")
label = tk.Label(
app,
font=("Helvetica", 16, "bold"),
)
label.pack()
def callback(text):
def closure():
label.config(text=text)
return closure
button = tk.Button(
app,
text="Приветствие",
command=callback("Hello, World!"),
)
button.pack()
app.mainloop()
def decorator(function):
def closure():
print("Делаем что-то до вызова функции.")
function()
print("Делаем что-то после вызова функции.")
return closure
@decorator
def greet():
print("Hi, username!")
greet()
# Делаем что-то до вызова функции.
# Hi, username!
# Делаем что-то после вызова функции.
def memoize(function):
cache = {}
def closure(number):
if number not in cache:
cache[number] = function(number)
return cache[number]
return closure
Примечание: Python включает мемоизацию в стандартную библиотеку. Если нужно делать кэширование, то можно использовать @cache или @lru_cache из модуля functools.
from time import sleep
def slow_operation(number):
sleep(0.5)
from timeit import timeit
timeit(
"[slow_operation(number) for number in [2, 3, 4, 2, 3, 4]]",
globals=globals(),
number=1,
)
# 3.02610950000053
@memoize
def slow_operation(number):
sleep(0.5)
timeit(
"[slow_operation(number) for number in [2, 3, 4, 2, 3, 4]]",
globals=globals(),
number=1,
)
# 1.5151869590008573
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def pop(self):
return self._items.pop()
from stack_v1 import Stack
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
stack.pop() # 3
stack._items # [1, 2]
def Stack():
_items = []
def push(item):
_items.append(item)
def pop():
return _items.pop()
def closure():
pass
closure.push = push
closure.pop = pop
return closure
from stack_v2 import Stack
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
stack.pop() # 3
stack._items
Traceback (most recent call last):
...
AttributeError: 'function' object has no attribute '_items'
stack.push.__closure__[0].cell_contents
# [1, 2]
def make_root_calculator(root_degree, precision=2):
def root_calculator(number):
return round(pow(number, 1 / root_degree), precision)
return root_calculator
square_root = make_root_calculator(2, 4)
square_root(42) # 6.4807
cubic_root = make_root_calculator(3)
cubic_root(42) # 3.48
class RootCalculator:
def __init__(self, root_degree, precision=2):
self.root_degree = root_degree
self.precision = precision
def __call__(self, number):
return round(pow(number, 1 / self.root_degree), self.precision)
from roots import RootCalculator
square_root = RootCalculator(2, 4)
square_root(42) # 6.4807
cubic_root = RootCalculator(3)
cubic_root(42) # 3.48
cubic_root.root_degree # 3
Источник: RealPython