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

Почему происходит замыкание функции в Python?

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

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

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

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

Замыкание (Closure) в Python

Замыкание — это мощный механизм в Python, который позволяет вложенной функции захватывать переменные из области видимости родительской функции. Расскажу, как это работает и почему это важно.

Базовый пример

def outer_function(x):
    # x — переменная в области видимости outer_function
    
    def inner_function(y):
        # inner_function имеет доступ к x из outer_function
        return x + y
    
    return inner_function

# Создаём замыкание
add_five = outer_function(5)
print(add_five(3))  # 8
print(add_five(10))  # 15

# Даже когда outer_function уже завершилась, 
# inner_function всё ещё имеет доступ к x!
print(add_five.__closure__)  # (<cell at ...: int object at ...>,)

Это замыкание! Функция inner_function запомнила значение x из внешней функции, даже после того как outer_function закончила выполняться.

Как это работает внутри?

Когда Python создаёт вложенную функцию, он проверяет, какие переменные из внешних областей видимости она использует. Эти переменные сохраняются в специальном объекте __closure__.

def multiplier(n):
    def multiply(x):
        return x * n
    return multiply

times_three = multiplier(3)
print(times_three.__closure__)  # (<cell at ...: int object at ...>,)
print(times_three.__closure__[0].cell_contents)  # 3

Тут мы видим, что замыкание хранит значение n в объекте cell. Каждый раз, когда вызываем times_three(5), она обращается к этому cell.

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

1. Декораторы — идеальный случай для замыканий

def decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Вызовов функции: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@decorator
def say_hello(name):
    return f"Привет, {name}!"

print(say_hello("Иван"))  # Печатает: Вызовов функции: say_hello

Здесь wrapper является замыканием — она запомнила func из области видимости decorator.

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

def create_greeter(greeting):
    def greet(name):
        return f"{greeting}, {name}!"
    return greet

english_greeter = create_greeter("Hello")
russian_greeter = create_greeter("Привет")

print(english_greeter("Alice"))  # Hello, Alice!
print(russian_greeter("Иван"))   # Привет, Иван!

Каждое замыкание сохранило своё значение greeting.

3. Функции высшего порядка

def make_adder(x):
    def adder(y):
        return x + y
    return adder

add_10 = make_adder(10)
add_20 = make_adder(20)

print(add_10(5))  # 15
print(add_20(5))  # 25

Потенциальная опасность: мутирующие замыкания

Есть классическая ошибка при работе с замыканиями в цикле:

# ❌ НЕПРАВИЛЬНО
functions = []
for i in range(3):
    def func():
        return i
    functions.append(func)

for f in functions:
    print(f())  # Печатает: 2, 2, 2 (не 0, 1, 2!)

# Почему? Все функции ссылаются на одну и ту же переменную i,
# и в конце цикла i = 2.

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

# ✅ ПРАВИЛЬНО
functions = []
for i in range(3):
    def func(x=i):  # x по умолчанию = текущее значение i
        return x
    functions.append(func)

for f in functions:
    print(f())  # Печатает: 0, 1, 2 ✓

Решение 2: Использовать замыкание как фабрику

# ✅ ПРАВИЛЬНО
def make_func(i):
    def func():
        return i
    return func

functions = [make_func(i) for i in range(3)]

for f in functions:
    print(f())  # Печатает: 0, 1, 2 ✓

Замыкание в сравнении с глобальными переменными

# ❌ Плохо: глобальное состояние
counter = 0

def increment():
    global counter
    counter += 1
    return counter

# ✅ Хорошо: замыкание инкапсулирует состояние
def create_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter1 = create_counter()
counter2 = create_counter()

print(counter1())  # 1
print(counter1())  # 2
print(counter2())  # 1 (у counter2 своё состояние)

Замыкание лучше глобальных переменных, потому что состояние инкапсулировано и не влияет на остальной код.

Когда использовать nonlocal

def make_bank_account(initial_balance):
    balance = initial_balance
    
    def deposit(amount):
        nonlocal balance  # Указываем, что меняем переменную родителя
        balance += amount
        return balance
    
    def withdraw(amount):
        nonlocal balance
        if amount <= balance:
            balance -= amount
            return balance
        return None
    
    return deposit, withdraw

deposit, withdraw = make_bank_account(1000)
print(deposit(500))    # 1500
print(withdraw(200))   # 1300

Вывод

Замыкание — это фундаментальная концепция функционального программирования. Оно позволяет:

  1. Инкапсулировать состояние
  2. Создавать декораторы
  3. Реализовывать паттерны вроде фабрик
  4. Избегать глобальных переменных

Понимание замыканий критично для писания чистого, гибкого Python-кода.

Почему происходит замыкание функции в Python? | PrepBro