← Назад к вопросам

Что замыкается в замыкании?

2.8 Senior🔥 181 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Замыкания (closures) в Python

Определение

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

Простой пример

def outer_function(x):
    """Внешняя функция"""
    
    def inner_function():  # Это будет замыканием
        """Вложенная функция имеет доступ к x"""
        return x + 10
    
    return inner_function

# Создаём замыкание
closure = outer_function(5)
print(closure())  # 15 — замыкание помнит x = 5

# Даже после завершения outer_function, closure помнит x
closure2 = outer_function(20)
print(closure2())  # 30 — это другое замыкание со своим x

Что замыкается? Переменная x из области видимости outer_function. Это и есть замыкание.

Что нужно знать о замыканиях

1. Замыкание захватывает переменные, а не значения

def create_multiplier(n):
    def multiply(x):
        return x * n  # Замыкание захватывает переменную n
    return multiply

mult_by_3 = create_multiplier(3)
mult_by_5 = create_multiplier(5)

print(mult_by_3(10))  # 30 — использует свою n=3
print(mult_by_5(10))  # 50 — использует свою n=5

# Каждое замыкание имеет свой набор захватанных переменных

2. Замыкание может модифицировать внешние переменные

def counter():
    count = 0  # Переменная из внешней области
    
    def increment():
        nonlocal count  # Указываем, что используем переменную из внешней области
        count += 1
        return count
    
    return increment

cnt = counter()
print(cnt())  # 1
print(cnt())  # 2
print(cnt())  # 3

# Замыкание сохраняет состояние count между вызовами

3. nonlocal vs global

global_var = "глобальная"

def outer():
    outer_var = "внешняя"
    
    def inner():
        local_var = "локальная"
        
        # global_var — глобальная переменная
        # outer_var — из замыкания (nonlocal)
        # local_var — локальная переменная
        
        print(f"Global: {global_var}")
        print(f"Outer: {outer_var}")
        print(f"Local: {local_var}")
    
    return inner

f = outer()
f()
# Global: глобальная
# Outer: внешняя
# Local: локальная

Различие:

  • nonlocal — переменная из немедленно охватывающей области (родительская функция)
  • global — переменная из глобальной области модуля

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

1. Декораторы (частый случай замыканий)

def timer_decorator(func):
    """Декоратор использует замыкание для доступа к func"""
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)  # Замыкание захватывает func
        print(f"Время: {time.time() - start:.4f}s")
        return result
    return wrapper

@timer_decorator
def slow_function():
    import time
    time.sleep(1)
    return "готово"

slow_function()
# Время: 1.0023s
# готово

2. Кэширование (мемоизация)

def memoize(func):
    """Замыкание запоминает результаты вызовов"""
    cache = {}  # Захватана в замыканию
    
    def wrapper(x):
        if x not in cache:
            cache[x] = func(x)  # Сохраняем результат
        return cache[x]
    
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(35))  # Быстро, благодаря кэшу в замыканию

3. Фабрика функций

def power_factory(exp):
    """Создаёт функцию возведения в степень"""
    def power(base):
        return base ** exp  # Замыкание захватывает exp
    return power

square = power_factory(2)
cube = power_factory(3)

print(square(5))  # 25 (5^2)
print(cube(5))    # 125 (5^3)

4. Частичное применение (currying)

def add(a):
    """Замыкание для создания функции сложения"""
    def inner(b):
        return a + b  # Замыкание захватывает a
    return inner

add_5 = add(5)
print(add_5(3))   # 8
print(add_5(10))  # 15

# Это замыкание, создающее специализированные функции

Визуализация замыкания

def outer(x):
    print(f"outer() вызвана с x={x}")
    
    def inner():
        print(f"inner() вызвана, x всё ещё {x}")
        return x
    
    print("outer() возвращает inner")
    return inner

# Вызов outer
closure = outer(42)
print("\n---\n")

# Вызов замыкания
result = closure()

# Выход:
# outer() вызвана с x=42
# outer() возвращает inner
# 
# ---
# 
# inner() вызвана, x всё ещё 42

# x="42" всё ещё доступен в замыканию, даже после outer завершилась!

Просмотр захватанных переменных

def create_greeting(name):
    age = 25  # Эта переменная будет захвачена
    
    def greet():
        return f"Привет, {name}, тебе {age} лет"
    
    return greet

closure = create_greeting("Alice")

# Просмотр захватанных переменных
print(closure.__closure__)  # Кортеж cell объектов
print(closure.__code__.co_freevars)  # Имена свободных переменных

# Выход:
# (<cell at ...: str object at ...>, <cell at ...: int object at ...>)
# ('name', 'age')

# Доступ к значениям:
for i, var_name in enumerate(closure.__code__.co_freevars):
    cell = closure.__closure__[i]
    print(f"{var_name} = {cell.cell_contents}")

# Выход:
# name = Alice
# age = 25

Изменение переменных в замыканию

def create_counter():
    count = 0  # Локальная переменная
    
    def increment():
        nonlocal count  # ВАЖНО: без nonlocal Python не позволит присваивать
        count += 1
        return count
    
    def decrement():
        nonlocal count
        count -= 1
        return count
    
    def get_count():
        return count
    
    # Возвращаем объект с методами
    return {
        'increment': increment,
        'decrement': decrement,
        'get': get_count
    }

counter = create_counter()
print(counter['increment']())  # 1
print(counter['increment']())  # 2
print(counter['get']())        # 2
print(counter['decrement']())  # 1

Частая ошибка с замыканиями в циклах

# НЕПРАВИЛЬНО: все замыкания делят одну переменную
def create_functions():
    functions = []
    for i in range(5):
        def func():
            return i  # Замыкание захватывает i
        functions.append(func)
    return functions

funcs = create_functions()
for func in funcs:
    print(func())  # Выведет: 4, 4, 4, 4, 4 (все используют последнее значение i)

# ПРАВИЛЬНО: Создай отдельное замыкание для каждого значения
def create_functions_correct():
    functions = []
    for i in range(5):
        def func(x=i):  # Захватываем значение через параметр по умолчанию
            return x
        functions.append(func)
    return functions

funcs = create_functions_correct()
for func in funcs:
    print(func())  # Выведет: 0, 1, 2, 3, 4 (правильно)

# ИЛИ используй lambda с параметром
def create_functions_lambda():
    functions = []
    for i in range(5):
        functions.append(lambda x=i: x)
    return functions

funcs = create_functions_lambda()
for func in funcs:
    print(func())  # Выведет: 0, 1, 2, 3, 4 (правильно)

Заключение

Что замыкается в замыканию:

  1. Переменные из внешней области видимости — функция запоминает их
  2. Состояние — замыкание может сохранять данные между вызовами
  3. Контекст выполнения — доступ к переменным сохраняется даже после завершения внешней функции

Запомни:

  • Замыкание = вложенная функция + захватанные переменные
  • Используй nonlocal для изменения переменных из внешней области
  • Будь осторожен с циклами (захватываются переменные, а не значения)
  • Замыкания часто используются в декораторах, кэшировании, фабриках функций
Что замыкается в замыкании? | PrepBro