Замыкания (Closures): функции с внутренним состоянием

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

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

👉 Простейший пример:

def outer_func():
    x = 10  # Локальная переменная

    def inner_func():
        print(x)  # Запоминает x из внешней функции

    return inner_func

my_func = outer_func()
my_func()  # Выведет 10, хотя x уже "не существует"!

Здесь inner_func "замкнула" переменную x, сохранив доступ к ней даже после завершения outer_func.


🧠 Как это работает?

Замыкание хранит три вещи: 1. Ссылку на функцию 2. Её область видимости (лексическое окружение) 3. Все нелокальные переменные, которые она использует

💡 Важно: Замыкание — это не сама функция, а совокупность функции и её окружения.


🛠 Практическое применение: счётчики и генераторы

🔢 Создаём счётчик с состоянием

Вместо классов можно использовать замыкания для хранения состояния:

def make_counter():
    count = 0

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

    return counter

counter1 = make_counter()
print(counter1())  # 1
print(counter1())  # 2

🌡️ Генератор температур

def temperature_generator(start_temp):
    current_temp = start_temp

    def get_temp(change):
        nonlocal current_temp
        current_temp += change
        return round(current_temp, 1)

    return get_temp

thermo = temperature_generator(20.0)
print(thermo(1.5))  # 21.5
print(thermo(-0.5))  # 21.0

🔥 Продвинутые примеры

🔐 Создаём "секретную" функцию

def secret_keeper(secret):
    def reveal(password):
        if password == "12345":
            return secret
        return "Access denied!"
    return reveal

vault = secret_keeper("Danila's secret Python tips")
print(vault("1111"))  # Access denied!
print(vault("12345"))  # Danila's secret Python tips

🎨 Фабрика функций для математических операций

def operation_factory(operator):
    def calculate(a, b):
        if operator == '+':
            return a + b
        elif operator == '*':
            return a * b
        elif operator == '^':
            return a ** b
    return calculate

pow_func = operation_factory('^')
print(pow_func(2, 3))  # 8

⚠️ Осторожно с изменяемыми объектами!

Замыкания хранят ссылки на переменные, а не их значения:

def create_functions():
    return [lambda x: x + i for i in range(3)]  # Опасно!

funcs = create_functions()
print([f(10) for f in funcs])  # [12, 12, 12] (а не [10, 11, 12]!)

Как исправить:

def create_functions_fixed():
    return [lambda x, i=i: x + i for i in range(3)]  # Фиксируем значение i

funcs = create_functions_fixed()
print([f(10) for f in funcs])  # [10, 11, 12] ✅

🏆 Главные преимущества замыканий

  1. Инкапсуляция без классов — скрываем "лишние" переменные
  2. Сохранение состояния между вызовами
  3. Гибкость — можно создавать функции на лету
  4. Читаемость — часто код выглядит чище, чем ООП-версия

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

✅ Для небольших объектов с одним методом
✅ Когда нужно сохранить простое состояние
✅ Для создания специализированных функций

Когда лучше класс?
❌ Нужно много методов
❌ Требуется наследование
❌ Сложное состояние объекта

Как говорит Данила Бежин в своих видео: "Замыкания — это мощный инструмент, но не серебряная пуля. Используйте их там, где они действительно упрощают код!" (https://www.youtube.com/@DanilaBezhin)


🧩 Проверь себя

  1. Что выведет этот код?
def creator():
    x = []
    def inner(y):
        x.append(y)
        return sum(x) / len(x)
    return inner

avg = creator()
print(avg(10))
print(avg(20))
Скрыть рекламу навсегда

📘 VK Видео — обучение без ограничений

Все уроки доступны без VPN, без блокировок и зависаний.

Можно смотреть с телефона, планшета или компьютера — в любое время.

▶️ Смотреть на VK Видео