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

Как реализуется замыкание в Python?

2.0 Middle🔥 181 комментариев
#Python Core

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

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

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

Как реализуется замыкание в Python?

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

Основной принцип

def outer():
    x = 10  # Переменная во внешней области видимости
    
    def inner():
        print(x)  # inner имеет доступ к x
    
    return inner

closure_func = outer()
closure_func()  # Выведет: 10
# Даже после завершения outer(), inner помнит x

Этот пример показывает базовый механизм замыкания. Функция inner запоминает переменную x из области видимости функции outer.

Как это работает под капотом

В Python функции имеют специальный атрибут __closure__, который содержит ячейки (cells) с переменными из внешней области:

def outer(x):
    def inner():
        return x * 2
    return inner

func = outer(5)
print(func.__closure__)  # (<cell at 0x...: int object at 0x...>,)
print(func.__closure__[0].cell_contents)  # 5

Каждая ячейка хранит ссылку на переменную. Это позволяет функции помнить значение переменной.

Несколько переменных в замыкании

def multiplier(factor):
    def multiply(number):
        return number * factor
    return multiply

# Создаём несколько замыканий с разными factor
double = multiplier(2)
triple = multiplier(3)
quadruple = multiplier(4)

print(double(5))       # 10
print(triple(5))       # 15
print(quadruple(5))    # 20

Каждое замыкание имеет собственную копию переменной factor. Это создаёт приватное состояние для каждой функции.

Проблема с изменяемыми переменными

# ❌ Проблема: неожиданное поведение
functions = []
for i in range(3):
    def func():
        return i
    functions.append(func)

for f in functions:
    print(f())  # Выведет: 2, 2, 2 (все смотрят на последнее значение i)

Все замыкания смотрят на одну и ту же переменную i, которая изменяется в цикле.

Решение 1: Используй значение по умолчанию

functions = []
for i in range(3):
    def func(x=i):  # Захватываем текущее значение i
        return x
    functions.append(func)

for f in functions:
    print(f())  # Выведет: 0, 1, 2 ✓

Решение 2: Используй фабрику функций

def make_func(x):
    def func():
        return x
    return func

functions = [make_func(i) for i in range(3)]
for f in functions:
    print(f())  # Выведет: 0, 1, 2 ✓

Замыкания с изменяемым состоянием

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

inc, get = counter()
print(inc())  # 1
print(inc())  # 2
print(inc())  # 3
print(get())  # 3

Ключевое слово nonlocal позволяет изменять переменные из замыкания. Это создаёт объект с состоянием (как приватная переменная в классе).

Декораторы как применение замыканий

Декораторы — это замыкания, которые оборачивают функции:

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        print(f"Время выполнения: {time.time() - start}")
        return result
    return wrapper

@timing_decorator
def slow_function():
    import time
    time.sleep(1)
    return "Done"

slow_function()
# wrapper замыкает func и может работать с ней

Практический пример: функция-конфигуратор

def create_logger(name, level):
    def log(message):
        print(f"[{name}] [{level}] {message}")
    return log

logger_info = create_logger("app", "INFO")
logger_error = create_logger("app", "ERROR")

logger_info("Application started")   # [app] [INFO] Application started
logger_error("An error occurred")    # [app] [ERROR] An error occurred

Производительность замыканий

Замыкания имеют небольшой overhead:

import dis

def with_closure():
    x = 10
    def inner():
        return x
    return inner

def without_closure():
    x = 10
    return lambda: x + 0  # Более простой путь доступа

# Замыкания работают через LOAD_DEREF (косвенный доступ)
# Это немного медленнее, чем прямой доступ к локальной переменной

Ключевые моменты

  1. Область видимости: замыкание запоминает переменные из внешней области
  2. nonlocal: необходимо для изменения переменных замыкания
  3. Приватное состояние: замыкания создают приватные переменные без классов
  4. Декораторы: основаны на замыканиях
  5. Memory overhead: замыкание занимает память для хранения переменных

Замыкания — мощный инструмент для создания функций высшего порядка, декораторов и реализации функционального стиля программирования в Python.

Как реализуется замыкание в Python? | PrepBro