📖 Теория: вложенные функции и замыкания

⚡ Кратко

Функция в Python может быть определена внутри другой функции. Внутренняя функция видит переменные внешней через Enclosing-область. Если внутренняя функция возвращается наружу и запоминает эти переменные, получается замыкание. Для изменения переменных внешней области нужно nonlocal.

  • LEGB: Local → Enclosing → Global → Built-in.
  • Присваивание внутри вложенной функции создаёт локальную переменную, если не использовать nonlocal.
  • Замыкание — функция + захваченное окружение.

Вложенные функции

В Python одна функция может быть определена внутри другой. Внутренняя функция называется вложенной (nested). Она полезна, когда нужно спрятать вспомогательную логику внутри основной функции.

def outer_function():
    print("Inside outer function")

    def inner_function():
        print("Inside inner function")

    inner_function()


outer_function()
# inner_function()  # NameError: name 'inner_function' is not defined
💡 На заметку: вложенная функция недоступна снаружи внешней. Это защищает вспомогательный код от случайного использования и конфликтов имён.

Зачем нужны вложенные функции

  • Инкапсуляция логики — вспомогательный код спрятан внутри основной функции.
  • Локальная область видимости — переменные вложенной функции не мешают глобальному пространству имён.
  • Избегание дублирования — один и тот же вспомогательный код можно вызывать несколько раз внутри внешней функции.

Область видимости Enclosing и LEGB

Python ищет имена в следующем порядке:

  1. Local — локальная область текущей функции.
  2. Enclosing — ближайшая внешняя функция, если текущая вложена.
  3. Global — глобальная область модуля.
  4. Built-in — встроенные имена Python.
def outer_function(repeat: int) -> None:
    message = "Outer function\n"

    def inner_function() -> None:
        print(message * repeat)  # переменные из Enclosing-области

    inner_function()


outer_function(3)

Локальные переменные затеняют внешние

Если во вложенной функции присвоить значение имени, совпадающему с именем во внешней функции, создаётся новая локальная переменная. Внешняя переменная не изменится.

def outer_function() -> None:
    message = "Outer function"

    def inner_function() -> None:
        message = "Inner function"  # новая локальная переменная

    inner_function()
    print(message)  # Outer function


outer_function()

Ключевое слово nonlocal

Если нужно изменить переменную внешней функции изнутри вложенной, используйте nonlocal. Оно указывает, что имя принадлежит ближайшей Enclosing-области, а не локальной.

def outer_function() -> None:
    message = "Outer function"

    def inner_function() -> None:
        nonlocal message
        message = "Changed inside inner function"

    inner_function()
    print(message)  # Changed inside inner function


outer_function()
⚠️ Важно: nonlocal работает только для ближайшей внешней функции. Оно не поднимается до глобальной области. Для глобальных переменных используйте global.

Замыкания

Замыкание (closure) — это объект, содержащий функцию и сохранённое окружение: переменные из Enclosing-области, которые остаются доступными даже после завершения внешней функции.

def outer_function(text: str):
    def inner_function() -> None:
        print(text)  # захваченная переменная

    return inner_function


closure = outer_function("Captured text")
print(closure)  # <function outer_function.<locals>.inner_function ...>
closure()       # Captured text

Свободные переменные

Переменные, которые используются во вложенной функции, но определены во внешней, называются свободными переменными (free variables). Именно они хранятся в замыкании.

def make_counter():
    count = 0

    def increment() -> int:
        nonlocal count
        count += 1
        return count

    return increment


counter = make_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

Фабрики функций

Замыкания часто используются для создания семейства похожих функций с разными настройками. Такой приём называется фабрикой функций.

def multiplier(factor: float):
    def multiply(number: float) -> float:
        return number * factor

    return multiply


double = multiplier(2)
triple = multiplier(3)

print(double(4))  # 8
print(triple(4))  # 12

Ловушка позднего связывания

Самая частая ошибка с замыканиями — создавать их внутри цикла, не передавая текущее значение переменной внутрь. Python связывает имя переменной, а не её значение, поэтому все замыкания получат последнее значение цикла.

def make_multipliers_wrong():
    return [lambda x: x * i for i in range(5)]


for func in make_multipliers_wrong():
    print(func(2))  # 8 8 8 8 8, а не 0 2 4 6 8

Исправление — захватить текущее значение через параметр по умолчанию:

def make_multipliers_right():
    return [lambda x, i=i: x * i for i in range(5)]


for func in make_multipliers_right():
    print(func(2))  # 0 2 4 6 8
Проверить по документации: подробнее о позднем связывании и замыканиях см. Python FAQ: lambdas in a loop.

Функция как объект

В Python функция — это объект, у которого есть служебные атрибуты. К ним можно обращаться напрямую.

def greet(name: str) -> None:
    """Greeting function."""
    print(f"Hello, {name}!")


print(greet.__name__)  # greet
print(greet.__doc__)   # Greeting function.


def make_multiplier(factor: float):
    def multiply(number: float) -> float:
        return number * factor
    return multiply


double = make_multiplier(2)
print(double.__closure__)                  # (<cell at ...>,)
print(double.__closure__[0].cell_contents)  # 2
Проверить по документации: полный список атрибутов функций описан в Data model.

Декораторы как частный случай замыканий

Декоратор — это функция, которая принимает другую функцию, добавляет к ней поведение и возвращает новую функцию. Внутри декоратора обычно используется вложенная функция-обёртка, поэтому декораторы — прямое применение замыканий.

def simple_decorator(func):
    def wrapper():
        print("Before call")
        func()
        print("After call")
    return wrapper


@simple_decorator
def say_hello():
    print("Hello!")


say_hello()

Синтаксис @simple_decorator эквивалентен записи say_hello = simple_decorator(say_hello). Обёртка wrapper захватывает func в замыкании.

Проверить по документации: подробное описание декораторов и functools.wraps — в глоссарии Python.