Почему происходит замыкание функции в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Замыкание (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
Вывод
Замыкание — это фундаментальная концепция функционального программирования. Оно позволяет:
- Инкапсулировать состояние
- Создавать декораторы
- Реализовывать паттерны вроде фабрик
- Избегать глобальных переменных
Понимание замыканий критично для писания чистого, гибкого Python-кода.