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

Что такое Nonlocal?

1.8 Middle🔥 141 комментариев
#Python Core

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

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

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

# Nonlocal в Python: углублённое объяснение

Определение

nonlocal — это ключевое слово Python, которое позволяет функции обращаться и модифицировать переменные из окружающей (enclosing) области видимости. Это отличается от глобальных переменных (которые находятся в глобальной области видимости) и от локальных переменных (которые существуют только в текущей функции).

Контекст: области видимости в Python

Прежде чем объяснить nonlocal, нужно понимать 4 уровня областей видимости (LEGB):

  1. Local — локальная область (текущая функция)
  2. Enclosing — окружающая область (функция-родитель)
  3. Global — глобальная область (модуль)
  4. 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.