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

Какие плюсы и минусы замыкания?

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

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

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

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

Замыкания в Python: плюсы и минусы

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

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

def make_adder(x):
    def adder(y):
        return x + y  # Доступ к x из внешней функции
    return adder

add_5 = make_adder(5)
print(add_5(10))  # 15
print(add_5(20))  # 25

Плюсы замыканий

1. Инкапсуляция и скрытие данных

Замыкания позволяют скрыть внутренние переменные от глобальной области видимости.

# Без замыкания — переменная видна везде
counter = 0

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

increment()  # Легко случайно изменить counter = 999

# С замыканием — инкапсуляция
def make_counter():
    count = 0  # Скрыта внутри
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    return increment

counter = make_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3
# count недоступна снаружи

2. Фабрики функций (Function Factories)

Легко создавать функции с разными параметрами.

def power_factory(exponent):
    def power(base):
        return base ** exponent
    return power

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

print(square(5))  # 25
print(cube(5))    # 125

# Это проще, чем:
class Power:
    def __init__(self, exponent):
        self.exponent = exponent
    
    def __call__(self, base):
        return base ** self.exponent

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

Замыкания — основа для декораторов.

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"Execution time: {elapsed:.4f} seconds")
        return result
    return wrapper

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

slow_function()  # Печатает время выполнения

4. Состояние без класса

Иногда замыкание проще класса.

# С замыканием
def make_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": deposit, "withdraw": withdraw}

account = make_account(1000)
print(account["deposit"](500))    # 1500
print(account["withdraw"](200))   # 1300

# Вместо:
class Account:
    def __init__(self, initial_balance):
        self.balance = initial_balance
    
    def deposit(self, amount):
        self.balance += amount
        return self.balance
    
    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            return self.balance
        return None

5. Каррирование

Трансформация функции с несколькими аргументами в последовательность функций с одним аргументом.

def multiply(x):
    def inner(y):
        def inner2(z):
            return x * y * z
        return inner2
    return inner

result = multiply(2)(3)(4)  # 24

# Или через functools.partial
from functools import partial

def multiply_three(x, y, z):
    return x * y * z

multiply_by_2 = partial(multiply_three, 2)
multiply_by_2_and_3 = partial(multiply_by_2, 3)
result = multiply_by_2_and_3(4)  # 24

6. Мемоизация

Кэширование результатов вычислений.

def memoize(func):
    cache = {}  # Замыкание запомнит этот словарь
    
    def wrapper(n):
        if n not in cache:
            cache[n] = func(n)
        return cache[n]
    
    return wrapper

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

print(fibonacci(100))  # Быстро благодаря кэшу

Минусы замыканий

1. Сложность для чтения и отладки

Замыкания могут быть трудными для понимания.

# Сложно следить за потоком выполнения
def outer(x):
    def middle(y):
        def inner(z):
            def deep(w):
                return x + y + z + w
            return deep
        return inner
    return middle

result = outer(1)(2)(3)(4)  # 10, но сложно читать

# Проще с классом:
class Calculator:
    def __init__(self, x):
        self.x = x
    
    def add_y(self, y):
        self.y = y
        return self
    
    def add_z(self, z):
        self.z = z
        return self
    
    def add_w(self, w):
        return self.x + self.y + self.z + w

result = Calculator(1).add_y(2).add_z(3).add_w(4)  # Яснее

2. Утечки памяти

Замыкание держит ссылку на переменные, препятствуя сборке мусора.

def make_large_array():
    large_array = list(range(1000000))  # Большой массив
    
    def get_first():
        return large_array[0]
    
    return get_first  # Функция держит ссылку на весь массив!

func = make_large_array()
print(func())  # 0
# large_array не удалится из памяти, даже если не используется

# Решение:
def make_large_array():
    large_array = list(range(1000000))
    first = large_array[0]  # Копируем только нужное значение
    
    def get_first():
        return first
    
    return get_first  # Теперь large_array будет удален

3. Проблемы с nonlocal и mutable состоянием

Делении состояния между замыканиями может быть опасным.

# Проблема: shared state
def make_functions():
    value = 0  # Общее состояние
    
    def increment():
        nonlocal value
        value += 1
        return value
    
    def decrement():
        nonlocal value
        value -= 1
        return value
    
    return increment, decrement

inc, dec = make_functions()
print(inc())  # 1
print(dec())  # 0
print(inc())  # 1

# Сложно следить за состоянием через несколько функций

# Лучше с классом:
class Counter:
    def __init__(self):
        self.value = 0
    
    def increment(self):
        self.value += 1
        return self.value
    
    def decrement(self):
        self.value -= 1
        return self.value

4. Проблема с цикловыми замыканиями

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

# Плохо - все функции ссылаются на один i
functions = []
for i in range(3):
    def func():
        return i  # Ошибка: i всегда последнее значение
    functions.append(func)

for f in functions:
    print(f())  # 2, 2, 2 вместо 0, 1, 2

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

for f in functions:
    print(f())  # 0, 1, 2 - правильно!

# Решение 2: factory function
functions = []
for i in range(3):
    def make_func(val):
        def func():
            return val
        return func
    functions.append(make_func(i))

for f in functions:
    print(f())  # 0, 1, 2 - правильно!

5. Сложность с многопоточностью

Замыкания с mutable состоянием опасны в многопоточной среде.

def make_unsafe_counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1  # Race condition!
        return count
    
    return increment

import threading

counter = make_unsafe_counter()
threads = []
for _ in range(100):
    t = threading.Thread(target=lambda: [counter() for _ in range(10)])
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(counter())  # Может быть не 1000 из-за race condition

# Решение: использовать Lock
import threading

def make_safe_counter():
    count = 0
    lock = threading.Lock()
    
    def increment():
        nonlocal count
        with lock:
            count += 1
            return count
    
    return increment

Когда использовать замыкания?

Используй замыкания для:

  • Декораторов
  • Фабрик функций
  • Простых callback-ов
  • Мемоизации
  • Инкапсуляции простых данных

Не используй замыкания для:

  • Сложной бизнес-логики (используй классы)
  • Shared mutable state между несколькими функциями (используй классы)
  • Больших объемов данных (утечки памяти)
  • Многопоточного кода без синхронизации

Рекомендация

У каждого инструмента есть своя область применения. Замыкания — отличны для функционального программирования, но для более сложного состояния лучше использовать классы.