Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Nonlocal в Python: углублённое объяснение
Определение
nonlocal — это ключевое слово Python, которое позволяет функции обращаться и модифицировать переменные из окружающей (enclosing) области видимости. Это отличается от глобальных переменных (которые находятся в глобальной области видимости) и от локальных переменных (которые существуют только в текущей функции).
Контекст: области видимости в Python
Прежде чем объяснить nonlocal, нужно понимать 4 уровня областей видимости (LEGB):
- Local — локальная область (текущая функция)
- Enclosing — окружающая область (функция-родитель)
- Global — глобальная область (модуль)
- Built-in — встроенная область (встроенные функции Python)
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # выведет "local"
inner()
print(x) # выведет "enclosing"
outer()
print(x) # выведет "global"
Проблема: почему нужен nonlocal?
Попробуем изменить переменную из enclosing области без nonlocal:
def counter():
count = 0
def increment():
count = count + 1 # UnboundLocalError!
return count
return increment
fn = counter()
fn() # ошибка: local variable 'count' referenced before assignment
Почему ошибка? Когда Python видит присваивание count = count + 1, он рассматривает count как локальную переменную в функции increment(). Но мы пытаемся использовать её до определения.
Решение: nonlocal
def counter():
count = 0
def increment():
nonlocal count # "используй count из enclosing scope"
count = count + 1
return count
return increment
fn = counter()
print(fn()) # 1
print(fn()) # 2
print(fn()) # 3
Теперь всё работает! nonlocal count говорит Python: "используй переменную count из окружающей функции, не создавай новую локальную".
Практические примеры
Пример 1: Замыкание (Closure)
def make_multiplier(n):
"""Возвращает функцию, которая умножает на n"""
def multiplier(x):
return x * n
return multiplier
times3 = make_multiplier(3)
print(times3(10)) # 30
times5 = make_multiplier(5)
print(times5(10)) # 50
Здесь n — переменная из enclosing scope, но мы не меняем её, поэтому nonlocal не нужен.
Пример 2: Счётчик с состоянием
def create_counter(start=0):
"""Создаёт счётчик с сохранением состояния"""
value = start
def increment():
nonlocal value
value += 1
return value
def decrement():
nonlocal value
value -= 1
return value
def get():
return value
return {'increment': increment, 'decrement': decrement, 'get': get}
counter = create_counter(10)
print(counter['increment']()) # 11
print(counter['increment']()) # 12
print(counter['decrement']()) # 11
print(counter['get']()) # 11
Пример 3: Декоратор с состоянием
def call_counter():
"""Декоратор, который считает вызовы функции"""
calls = 0
def decorator(func):
def wrapper(*args, **kwargs):
nonlocal calls
calls += 1
print(f"Функция вызвана {calls} раз")
return func(*args, **kwargs)
wrapper.call_count = lambda: calls
return wrapper
return decorator
@call_counter()
def greet(name):
return f"Привет, {name}!"
print(greet("Иван")) # Функция вызвана 1 раз
print(greet("Мария")) # Функция вызвана 2 раз
Пример 4: Валидатор с кешированием
def create_validator(min_val, max_val):
cache = {}
def validate(value):
nonlocal cache
# Проверяем кеш
if value in cache:
return cache[value]
# Вычисляем результат
result = min_val <= value <= max_val
# Сохраняем в кеш (изменяем переменную enclosing scope)
cache[value] = result
return result
def get_cache_size():
return len(cache)
validate.cache_size = get_cache_size
return validate
validate_age = create_validator(0, 120)
print(validate_age(25)) # True
print(validate_age(150)) # False
print(validate_age(25)) # True (из кеша)
print(validate_age.cache_size()) # 2
Отличие от global
x = "global"
def outer():
x = "enclosing"
def inner():
global x
x = "modified global"
inner()
print(x) # всё ещё "enclosing", потому что global меняет глобальный x
outer()
print(x) # "modified global"
# С nonlocal
def outer2():
x = "enclosing"
def inner():
nonlocal x
x = "modified enclosing"
inner()
print(x) # "modified enclosing"
outer2()
Когда использовать nonlocal?
✅ Используй nonlocal, когда:
- Нужно сохранить состояние в замыкании (closure)
- Пишешь декоратор, который отслеживает состояние
- Создаёшь фабрику функций с внутренним состоянием
- Работаешь с вложенными функциями и нужно изменить переменную родителя
❌ Не используй nonlocal, когда:
- Можешь использовать параметры функции
- Можешь использовать классы (часто это лучше)
- Код становится сложным для понимания
Когда использовать классы вместо nonlocal?
Часто замыкания с nonlocal можно переписать через классы, что будет понятнее:
# С nonlocal
def counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
# С классом (более явно)
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
counter = Counter()
print(counter()) # 1
print(counter()) # 2
Итоги
nonlocal — мощный инструмент для работы с замыканиями и сохранения состояния во вложенных функциях. Ключ к пониманию — помнить уровни областей видимости (LEGB) и использовать nonlocal только когда действительно нужно модифицировать переменные из enclosing scope.