Замыкания (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] ✅
🏆 Главные преимущества замыканий
- Инкапсуляция без классов — скрываем "лишние" переменные
- Сохранение состояния между вызовами
- Гибкость — можно создавать функции на лету
- Читаемость — часто код выглядит чище, чем ООП-версия
🤔 Когда использовать?
✅ Для небольших объектов с одним методом
✅ Когда нужно сохранить простое состояние
✅ Для создания специализированных функций
Когда лучше класс?
❌ Нужно много методов
❌ Требуется наследование
❌ Сложное состояние объекта
Как говорит Данила Бежин в своих видео: "Замыкания — это мощный инструмент, но не серебряная пуля. Используйте их там, где они действительно упрощают код!" (https://www.youtube.com/@DanilaBezhin)
🧩 Проверь себя
- Что выведет этот код?
def creator():
x = []
def inner(y):
x.append(y)
return sum(x) / len(x)
return inner
avg = creator()
print(avg(10))
print(avg(20))